@@ -697,5 +697,272 @@ function ($user, $password) {
697697 ->withErrors (['email ' => __ ($ status )]);
698698 }
699699
700+ public function xRedirect ()
701+ {
702+ try {
703+ Log::channel ('audit_trail ' )->info ('Redirecting to X for authentication. ' , [
704+ 'time ' => microtime (true )
705+ ]);
706+ return Socialite::driver ('x ' )->redirect ();
707+ } catch (Exception $ e ) {
708+ Log::error ('X redirect failed: ' . $ e ->getMessage (), [
709+ 'trace ' => $ e ->getTraceAsString ()
710+ ]);
711+ return redirect ()->route ('login ' )->with ('error ' , __ ('messages.error_x_auth_failed ' ));
712+ }
713+ }
714+
715+ public function xCallback (Request $ request ): RedirectResponse
716+ {
717+ $ time_start_callback = microtime (true );
718+ Log::channel ('audit_trail ' )->info ('X callback initiated. ' , [
719+ 'ip_address ' => $ request ->ip (),
720+ 'query_params ' => $ request ->query (),
721+ 'time_start_callback ' => $ time_start_callback
722+ ]);
723+
724+ try {
725+ if ($ request ->has ('error ' )) {
726+ Log::channel ('audit_trail ' )->error ('X callback returned an error. ' , [
727+ 'error ' => $ request ->input ('error ' ),
728+ 'error_description ' => $ request ->input ('error_description ' ),
729+ 'ip_address ' => $ request ->ip (),
730+ 'time ' => microtime (true ),
731+ ]);
732+ return redirect ()->route ('login ' )
733+ ->with ('error ' , __ ('messages.error_x_auth_denied_or_failed ' ));
734+ }
735+
736+ if (!$ request ->has ('code ' )) {
737+ Log::channel ('audit_trail ' )->error ('X callback missing authorization code. ' , [
738+ 'ip_address ' => $ request ->ip (),
739+ 'query_params ' => $ request ->query (),
740+ 'time ' => microtime (true ),
741+ ]);
742+ return redirect ()->route ('login ' )
743+ ->with ('error ' , __ ('messages.error_x_auth_failed ' ) . ' (Missing authorization code) ' );
744+ }
745+
746+ $ time_before_socialite_user = microtime (true );
747+ Log::channel ('audit_trail ' )->info ('Attempting to fetch X user from Socialite. ' , ['time ' => $ time_before_socialite_user ]);
748+
749+ $ xUser = Socialite::driver ('x ' )->stateless ()->user ();
750+
751+ $ time_after_socialite_user = microtime (true );
752+ $ duration_socialite_user_call = $ time_after_socialite_user - $ time_before_socialite_user ;
753+ Log::channel ('audit_trail ' )->info ('Successfully fetched X user from Socialite. ' , [
754+ 'x_user_id ' => $ xUser ->getId (),
755+ 'x_user_email ' => $ xUser ->getEmail (),
756+ 'time ' => $ time_after_socialite_user ,
757+ 'duration_socialite_user_call_seconds ' => $ duration_socialite_user_call
758+ ]);
759+
760+ $ time_before_db_lookup = microtime (true );
761+ $ userModel = User::where ('x_id ' , $ xUser ->getId ())->first ();
762+ $ time_after_db_lookup = microtime (true );
763+ Log::channel ('audit_trail ' )->info ('DB lookup for existing X ID. ' , [
764+ 'duration_db_lookup_seconds ' => $ time_after_db_lookup - $ time_before_db_lookup ,
765+ 'found_user_by_x_id ' => !is_null ($ userModel )
766+ ]);
767+
768+ $ action = "Logged in " ;
769+ $ initialUserModelNull = is_null ($ userModel );
770+
771+ if (!$ userModel ) {
772+ $ time_before_handle_user = microtime (true );
773+ Log::channel ('audit_trail ' )->info ('No existing user by X ID. Calling handleXUser. ' , [
774+ 'x_email ' => $ xUser ->getEmail (),
775+ 'time ' => $ time_before_handle_user
776+ ]);
777+
778+ $ userModel = $ this ->handleXUser ($ xUser );
779+
780+ $ time_after_handle_user = microtime (true );
781+ Log::channel ('audit_trail ' )->info ('Finished handleXUser call. ' , [
782+ 'user_id_returned ' => $ userModel ->id ,
783+ 'was_recently_created ' => $ userModel ->wasRecentlyCreated ,
784+ 'duration_handle_user_seconds ' => $ time_after_handle_user - $ time_before_handle_user
785+ ]);
786+
787+ if ($ userModel ->wasRecentlyCreated ) {
788+ $ action = 'Registered and logged in ' ;
789+ event (new UserRegistered ($ userModel ));
790+ } elseif ($ initialUserModelNull && $ userModel ->x_id == $ xUser ->getId ()) {
791+ $ action = 'Logged in (linked X to existing email account) ' ;
792+ } else {
793+ $ action = 'Logged in (handleXUser resolved) ' ;
794+ }
795+ } else {
796+ $ action = "Logged in (existing X ID) " ;
797+ }
798+
799+ $ user = $ userModel ;
800+
801+ Auth::login ($ user , true );
802+ $ request ->session ()->regenerate ();
803+
804+ $ time_end_callback = microtime (true );
805+ $ total_callback_duration = $ time_end_callback - $ time_start_callback ;
806+ Log::channel ('audit_trail ' )->info ("User $ action via X. " , [
807+ 'user_id ' => $ user ->id ,
808+ 'username ' => $ user ->username ,
809+ 'email ' => $ user ->email ,
810+ 'x_id ' => $ xUser ->getId (),
811+ 'ip_address ' => $ request ->ip (),
812+ 'time_end_callback ' => $ time_end_callback ,
813+ 'total_callback_duration_seconds ' => $ total_callback_duration
814+ ]);
815+
816+ return redirect ()->intended (route ('home ' ))->with ('success ' , __ ('messages.x_login_success ' ));
817+
818+ } catch (InvalidStateException $ e ) {
819+ Log::channel ('audit_trail ' )->error ('X authentication failed: Invalid State. ' , [
820+ 'error ' => $ e ->getMessage (),
821+ 'ip_address ' => $ request ->ip (),
822+ 'time_exception ' => microtime (true ),
823+ ]);
824+ Log::error ('X Auth Invalid State Error ' , [
825+ 'error ' => $ e ->getMessage (),
826+ 'trace ' => $ e ->getTraceAsString (),
827+ 'ip ' => $ request ->ip ()
828+ ]);
829+ return redirect ()->route ('login ' )
830+ ->with ('error ' , __ ('messages.error_x_auth_failed_state ' ) ?: 'X authentication failed due to an invalid state. Please try again. ' );
831+ } catch (ConnectException $ e ) {
832+ Log::channel ('audit_trail ' )->error ('X authentication failed: Connection issue (Guzzle). ' , [
833+ 'error ' => $ e ->getMessage (),
834+ 'ip_address ' => $ request ->ip (),
835+ 'time_exception ' => microtime (true ),
836+ ]);
837+ Log::error ('X Auth Guzzle ConnectException ' , [
838+ 'error ' => $ e ->getMessage (),
839+ 'trace ' => $ e ->getTraceAsString (),
840+ 'ip ' => $ request ->ip ()
841+ ]);
842+ return redirect ()->route ('login ' )
843+ ->with ('error ' , __ ('messages.error_x_auth_network ' ) ?: 'Could not connect to X for authentication. Please check your internet connection and try again. ' );
844+ } catch (Exception $ e ) {
845+ Log::channel ('audit_trail ' )->error ('X authentication/callback failed with generic Exception. ' , [
846+ 'error ' => $ e ->getMessage (),
847+ 'exception_type ' => get_class ($ e ),
848+ 'ip_address ' => $ request ->ip (),
849+ 'time_exception ' => microtime (true ),
850+ ]);
851+ Log::error ('X Auth System Error (Generic Exception) ' , [
852+ 'error ' => $ e ->getMessage (),
853+ 'exception_type ' => get_class ($ e ),
854+ 'trace ' => $ e ->getTraceAsString (),
855+ 'ip ' => $ request ->ip ()
856+ ]);
857+
858+ return redirect ()->route ('login ' )
859+ ->with ('error ' , __ ('messages.error_x_login_failed ' ));
860+ }
861+ }
862+
863+ private function handleXUser ($ xUser ): User
864+ {
865+ $ time_start_handle = microtime (true );
866+ Log::channel ('audit_trail ' )->info ('Inside handleXUser. ' , [
867+ 'x_email ' => $ xUser ->getEmail (),
868+ 'x_id_from_socialite ' => $ xUser ->getId (),
869+ 'time_start ' => $ time_start_handle
870+ ]);
871+
872+ $ existingUserByEmail = User::where ('email ' , $ xUser ->getEmail ())->first ();
873+ $ time_after_email_lookup = microtime (true );
874+ Log::channel ('audit_trail ' )->info ('DB lookup for existing email in handleXUser. ' , [
875+ 'duration_email_lookup_seconds ' => $ time_after_email_lookup - $ time_start_handle ,
876+ 'found_user_by_email ' => !is_null ($ existingUserByEmail )
877+ ]);
878+
879+ if ($ existingUserByEmail ) {
880+ Log::channel ('audit_trail ' )->info ('Existing user found by email in handleXUser. Updating with X ID if not set. ' , [
881+ 'user_id ' => $ existingUserByEmail ->id ,
882+ 'current_x_id_on_user ' => $ existingUserByEmail ->x_id
883+ ]);
884+ $ userToReturn = $ this ->updateExistingUserWithX ($ existingUserByEmail , $ xUser );
885+ $ log_action = 'updated_existing_user_with_x_info ' ;
886+ } else {
887+ Log::channel ('audit_trail ' )->info ('No existing user by email in handleXUser. Creating new user from X info. ' );
888+ $ userToReturn = $ this ->createUserFromX ($ xUser );
889+ $ log_action = 'created_new_user_from_x_info ' ;
890+ }
891+
892+ $ time_end_handle = microtime (true );
893+ Log::channel ('audit_trail ' )->info ('Finished handleXUser. ' , [
894+ 'user_id_processed ' => $ userToReturn ->id ,
895+ 'action_taken ' => $ log_action ,
896+ 'was_recently_created_flag ' => $ userToReturn ->wasRecentlyCreated ,
897+ 'final_x_id_on_user ' => $ userToReturn ->x_id ,
898+ 'duration_handle_user_total_seconds ' => $ time_end_handle - $ time_start_handle ,
899+ ]);
900+
901+ return $ userToReturn ;
902+ }
903+
904+ private function updateExistingUserWithX (User $ user , $ xUser ): User
905+ {
906+ if (is_null ($ user ->x_id )) {
907+ $ user ->x_id = $ xUser ->getId ();
908+ }
909+
910+ if (!$ user ->profile_picture && $ xUser ->getAvatar ()) {
911+ $ user ->profile_picture = $ xUser ->getAvatar ();
912+ }
913+
914+ $ user ->email_verified_at = $ user ->email_verified_at ?? now ();
915+ $ user ->save ();
916+ Log::channel ('audit_trail ' )->info ('Updated existing user with X info. ' , ['user_id ' => $ user ->id , 'x_id_set ' => $ user ->x_id ]);
917+ return $ user ;
918+ }
919+
920+ private function createUserFromX ($ xUser ): User
921+ {
922+ $ time_start_create = microtime (true );
923+ Log::channel ('audit_trail ' )->info ('Creating new user from X data. ' , [
924+ 'x_email ' => $ xUser ->getEmail (),
925+ 'x_name ' => $ xUser ->getName (),
926+ 'time_start ' => $ time_start_create
927+ ]);
928+
929+ $ username = $ this ->generateUniqueUsername ($ xUser ->getName () ?: $ xUser ->getNickname () ?: 'user ' );
930+
931+ $ name = $ xUser ->getName () ?: $ xUser ->getNickname () ?: '' ;
932+ $ nameParts = explode (' ' , $ name , 2 );
933+ $ firstName = $ nameParts [0 ] ?? Str::studly (Str::before ($ xUser ->getEmail () ?: 'user@x.com ' , '@ ' ));
934+ $ lastName = $ nameParts [1 ] ?? null ;
935+
936+ $ user = User::create ([
937+ 'first_name ' => $ firstName ,
938+ 'last_name ' => $ lastName ,
939+ 'username ' => $ username ,
940+ 'email ' => $ xUser ->getEmail () ?: $ username . '@x-user.local ' ,
941+ 'x_id ' => $ xUser ->getId (),
942+ 'email_verified_at ' => now (),
943+ 'password ' => Hash::make (Str::random (24 )),
944+ ]);
945+ Log::channel ('audit_trail ' )->info ('User model created in DB. ' , ['user_id ' => $ user ->id , 'username ' => $ username , 'time_after_eloquent_create ' => microtime (true )]);
946+
947+ if ($ xUser ->getAvatar ()) {
948+ $ user ->profile_picture = $ xUser ->getAvatar ();
949+ } else {
950+ $ profilePicturePath = $ this ->avatarService ->generateInitialsAvatar (
951+ $ user ->first_name ,
952+ $ user ->last_name ?? '' ,
953+ $ user ->id
954+ );
955+ $ user ->profile_picture = $ profilePicturePath ;
956+ }
957+ $ user ->save ();
958+
959+ $ time_end_create = microtime (true );
960+ Log::channel ('audit_trail ' )->info ('Finished creating user from X and saved profile picture. ' , [
961+ 'user_id ' => $ user ->id ,
962+ 'duration_create_user_seconds ' => $ time_end_create - $ time_start_create
963+ ]);
964+
965+ return $ user ;
966+ }
700967}
701968
0 commit comments