@@ -652,8 +652,12 @@ pub async fn accept_follow_request(
652652 }
653653 blind_and_send ( & their_identity, & their_boxer, & reply. build ( ) , poster) . await ?;
654654
655- // Persist their entry point so we retain read access after this session.
656- persist_friend_entry_point ( user, entry, store. clone ( ) , mutable) . await ?;
655+ // Persist their entry point only when reciprocating (mutual friendship), so we
656+ // get read access to their shared files — matching Java's `sendReplyFollowRequest`
657+ // which skips `addExternalEntryPoint` when `accept=true, reciprocate=false`.
658+ if reciprocate {
659+ persist_friend_entry_point ( user, entry, store. clone ( ) , mutable) . await ?;
660+ }
657661
658662 // Add them to our followers group, and — if reciprocating (mutual friends) —
659663 // our friends group, matching Java's accept flow. This shares each group's
@@ -669,6 +673,56 @@ pub async fn accept_follow_request(
669673 Ok ( ( ) )
670674}
671675
676+ /// `sendReplyFollowRequest` with `accept=false`: send a rejection reply (null
677+ /// entry point) back to the requester. If `reciprocate` is true the requester's
678+ /// read base key is echoed so they become a follower even though we didn't accept
679+ /// their request (Java's `accept=false, reciprocate=true` path).
680+ pub async fn reject_follow_request (
681+ user : & LoggedInUser ,
682+ request : & ReceivedFollowRequest ,
683+ reciprocate : bool ,
684+ poster : & dyn HttpPoster ,
685+ store : Arc < dyn ContentAddressedStorage > ,
686+ mutable : & dyn MutablePointers ,
687+ ) -> Result < ( ) > {
688+ let entry = request
689+ . entry
690+ . as_ref ( )
691+ . ok_or_else ( || Error :: Protocol ( "follow request has no entry point" . into ( ) ) ) ?;
692+ let their_name = entry. owner_name . clone ( ) ;
693+ let ( their_identity, their_boxer) =
694+ get_public_keys ( poster, store. as_ref ( ) , mutable, & their_name) . await ?;
695+
696+ // Null capability: all-zero identity, map_key, and r_base_key.
697+ let null_hash = PublicKeyHash :: identity ( vec ! [ 0u8 ; 32 ] ) ?;
698+ let null_key = SymmetricKey :: new ( vec ! [ 0u8 ; 32 ] , false ) ?;
699+ let null_cap = AbsoluteCapability {
700+ owner : null_hash,
701+ writer : PublicKeyHash :: identity ( vec ! [ 0u8 ; 32 ] ) ?,
702+ map_key : vec ! [ 0u8 ; 32 ] ,
703+ bat : None ,
704+ r_base_key : null_key,
705+ w_base_key : None ,
706+ } ;
707+ let null_entry = EntryPoint { pointer : null_cap. clone ( ) , owner_name : user. username . clone ( ) } ;
708+
709+ let mut reply = CborObject :: map ( ) . put ( "e" , null_entry. to_cbor ( ) ) ;
710+ if reciprocate {
711+ reply = reply. put ( "k" , entry. pointer . r_base_key . to_cbor ( ) ) ;
712+ }
713+ blind_and_send ( & their_identity, & their_boxer, & reply. build ( ) , poster) . await ?;
714+
715+ // If reciprocating, persist the requester's entry point so we can see their
716+ // shared files (Java's `addExternalEntryPoint` + `retrieveAndAddEntryPointToTrie`).
717+ if reciprocate {
718+ persist_friend_entry_point ( user, entry, store. clone ( ) , mutable) . await ?;
719+ add_member_to_group ( user, FOLLOWERS_GROUP , & their_name, store. clone ( ) , mutable) . await ?;
720+ }
721+
722+ remove_follow_request ( user, & request. raw_cipher , poster) . await ?;
723+ Ok ( ( ) )
724+ }
725+
672726/// Process a reply to one of our outgoing follow requests: persist the sender's
673727/// entry point and remove the request. (The reply needs no further response.)
674728pub async fn process_follow_reply (
0 commit comments