@@ -977,3 +977,113 @@ fn test_authorize_controlled_targets_passes_through_prespawned() {
977977 "authorize_controlled_targets stripped a PreSpawned target — it must pass through" ,
978978 ) ;
979979}
980+
981+ /// Documents the recommended way to run your own validator *after* the
982+ /// `authorize_controlled_targets` helper, so it only sees authorized targets:
983+ /// register it with `.after(authorize_controlled_targets::<S>)`.
984+ ///
985+ /// Client 0 controls A and forges a target on uncontrolled B. The custom
986+ /// validator, ordered after the helper, records the targets it sees: it must
987+ /// see A but never the spoofed B (already stripped). Were it unordered (or
988+ /// before), it could observe B.
989+ #[ test]
990+ fn test_custom_validator_ordered_after_authorize ( ) {
991+ use bevy:: ecs:: resource:: Resource ;
992+ use bevy:: ecs:: schedule:: IntoScheduleConfigs ;
993+ use bevy:: ecs:: system:: { Query , ResMut } ;
994+ use bevy:: prelude:: Entity ;
995+ use lightyear:: input:: leafwing:: input_message:: LeafwingSequence ;
996+ use lightyear_inputs:: input_message:: { InputMessage , InputTarget } ;
997+ use lightyear_inputs:: prelude:: server:: { InputValidationAppExt , authorize_controlled_targets} ;
998+ use lightyear_messages:: prelude:: MessageReceiver ;
999+ use lightyear_replication:: prelude:: ControlledBy ;
1000+
1001+ #[ derive( Resource , Default ) ]
1002+ struct SeenAfterAuth ( Vec < Entity > ) ;
1003+
1004+ fn record_after_auth (
1005+ mut seen : ResMut < SeenAfterAuth > ,
1006+ mut receivers : Query < & mut MessageReceiver < InputMessage < LeafwingSequence < LeafwingInput1 > > > > ,
1007+ ) {
1008+ for mut receiver in & mut receivers {
1009+ receiver. retain_messages ( |msg| {
1010+ for data in & msg. inputs {
1011+ if let InputTarget :: Entity ( e) = data. target {
1012+ seen. 0 . push ( e) ;
1013+ }
1014+ }
1015+ true
1016+ } ) ;
1017+ }
1018+ }
1019+
1020+ let mut stepper = ClientServerStepper :: from_config ( StepperConfig :: with_netcode_clients ( 1 ) ) ;
1021+ stepper. server_app . init_resource :: < SeenAfterAuth > ( ) ;
1022+ // Recommended ordering: register your validator `.after` the helper.
1023+ stepper
1024+ . server_app
1025+ . add_input_validator ( authorize_controlled_targets :: < LeafwingSequence < LeafwingInput1 > > ) ;
1026+ stepper. server_app . add_input_validator (
1027+ record_after_auth. after ( authorize_controlled_targets :: < LeafwingSequence < LeafwingInput1 > > ) ,
1028+ ) ;
1029+
1030+ let client_of_0 = stepper. client_of ( 0 ) . id ( ) ;
1031+ let entity_a = stepper
1032+ . server_app
1033+ . world_mut ( )
1034+ . spawn ( (
1035+ ActionState :: < LeafwingInput1 > :: default ( ) ,
1036+ Replicate :: to_clients ( NetworkTarget :: All ) ,
1037+ ControlledBy {
1038+ owner : client_of_0,
1039+ lifetime : Default :: default ( ) ,
1040+ } ,
1041+ ) )
1042+ . id ( ) ;
1043+ let entity_b = stepper
1044+ . server_app
1045+ . world_mut ( )
1046+ . spawn ( (
1047+ ActionState :: < LeafwingInput1 > :: default ( ) ,
1048+ Replicate :: to_clients ( NetworkTarget :: All ) ,
1049+ ) )
1050+ . id ( ) ;
1051+ stepper. frame_step ( 10 ) ;
1052+
1053+ let local_a = stepper
1054+ . client ( 0 )
1055+ . get :: < MessageManager > ( )
1056+ . unwrap ( )
1057+ . entity_mapper
1058+ . get_local ( entity_a)
1059+ . expect ( "A replicated" ) ;
1060+ let local_b = stepper
1061+ . client ( 0 )
1062+ . get :: < MessageManager > ( )
1063+ . unwrap ( )
1064+ . entity_mapper
1065+ . get_local ( entity_b)
1066+ . expect ( "B replicated" ) ;
1067+ for local in [ local_a, local_b] {
1068+ stepper. client_apps [ 0 ] . world_mut ( ) . entity_mut ( local) . insert (
1069+ InputMap :: < LeafwingInput1 > :: new ( [ ( LeafwingInput1 :: Jump , KeyCode :: KeyA ) ] ) ,
1070+ ) ;
1071+ }
1072+ stepper. frame_step ( 1 ) ;
1073+ stepper. client_apps [ 0 ]
1074+ . world_mut ( )
1075+ . resource_mut :: < ButtonInput < KeyCode > > ( )
1076+ . press ( KeyCode :: KeyA ) ;
1077+ stepper. frame_step ( 10 ) ;
1078+
1079+ let seen = & stepper. server_app . world ( ) . resource :: < SeenAfterAuth > ( ) . 0 ;
1080+ assert ! (
1081+ seen. contains( & entity_a) ,
1082+ "custom validator never saw the authorized target A — setup/ordering issue" ,
1083+ ) ;
1084+ assert ! (
1085+ !seen. contains( & entity_b) ,
1086+ "custom validator saw the spoofed target B; it ran before authorize_controlled_targets \
1087+ (the `.after(authorize_controlled_targets::<S>)` ordering didn't take effect)",
1088+ ) ;
1089+ }
0 commit comments