From fd1b3ab983287ac138597c0a19f75612a1d3a414 Mon Sep 17 00:00:00 2001
From: Miss Pasture <miss@pasture.moe>
Date: Wed, 14 Jul 2021 15:52:21 -0400
Subject: [PATCH] Set a maximum number of follows per user

---
 VARIABLES.md                                     |  1 +
 .../Settings/InstanceSettings.cs                 |  2 ++
 src/BirdsiteLive.Domain/UserService.cs           | 16 +++++++++++++++-
 src/BirdsiteLive/appsettings.json                |  5 +++--
 4 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/VARIABLES.md b/VARIABLES.md
index 8928513..35d5a56 100644
--- a/VARIABLES.md
+++ b/VARIABLES.md
@@ -51,6 +51,7 @@ If both whitelisting and blacklisting are set, only the whitelisting will be act
 * `Instance:TwitterDomainLabel` (default: "") if TwitterDomain is set, use this label on profile pages instead of the domain itself (i.e. you can set this to "Nitter" to show that on profiles instead of "twiiit.com")
 * `Instance:InfoBanner` (default: "") text to show in a banner on the front page
 * `Instance:ShowAboutInstanceOnProfiles` (default: true) show "About [instance name]" on profiles with a link to /About
+* `Instance:MaxFollowsPerUser` (default: 0 - no limit) limit the number of follows per user - any follow count above this number will be Rejected
 
 # Docker Compose full example
 
diff --git a/src/BirdsiteLive.Common/Settings/InstanceSettings.cs b/src/BirdsiteLive.Common/Settings/InstanceSettings.cs
index c2426e3..1d3d1f7 100644
--- a/src/BirdsiteLive.Common/Settings/InstanceSettings.cs
+++ b/src/BirdsiteLive.Common/Settings/InstanceSettings.cs
@@ -19,5 +19,7 @@
 
         public bool ShowAboutInstanceOnProfiles { get; set; }
 
+        public int MaxFollowsPerUser { get; set; }
+
     }
 }
\ No newline at end of file
diff --git a/src/BirdsiteLive.Domain/UserService.cs b/src/BirdsiteLive.Domain/UserService.cs
index ba9fee5..f0fa430 100644
--- a/src/BirdsiteLive.Domain/UserService.cs
+++ b/src/BirdsiteLive.Domain/UserService.cs
@@ -10,6 +10,7 @@ using BirdsiteLive.ActivityPub.Converters;
 using BirdsiteLive.Common.Regexes;
 using BirdsiteLive.Common.Settings;
 using BirdsiteLive.Cryptography;
+using BirdsiteLive.DAL.Contracts;
 using BirdsiteLive.Domain.BusinessUseCases;
 using BirdsiteLive.Domain.Repository;
 using BirdsiteLive.Domain.Statistics;
@@ -45,8 +46,10 @@ namespace BirdsiteLive.Domain
 
         private readonly IModerationRepository _moderationRepository;
 
+        private readonly IFollowersDal _followerDal;
+
         #region Ctor
-        public UserService(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService, IProcessFollowUser processFollowUser, IProcessUndoFollowUser processUndoFollowUser, IStatusExtractor statusExtractor, IExtractionStatisticsHandler statisticsHandler, ITwitterUserService twitterUserService, IModerationRepository moderationRepository)
+        public UserService(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService, IProcessFollowUser processFollowUser, IProcessUndoFollowUser processUndoFollowUser, IStatusExtractor statusExtractor, IExtractionStatisticsHandler statisticsHandler, ITwitterUserService twitterUserService, IModerationRepository moderationRepository, IFollowersDal followerDal)
         {
             _instanceSettings = instanceSettings;
             _cryptoService = cryptoService;
@@ -57,6 +60,7 @@ namespace BirdsiteLive.Domain
             _statisticsHandler = statisticsHandler;
             _twitterUserService = twitterUserService;
             _moderationRepository = moderationRepository;
+            _followerDal = followerDal;
         }
         #endregion
 
@@ -177,6 +181,16 @@ namespace BirdsiteLive.Domain
                     return await SendRejectFollowAsync(activity, followerHost);
             }
 
+            // Validate follower count < MaxFollowsPerUser
+            if (_instanceSettings.MaxFollowsPerUser > 0) {
+                var follower = await _followerDal.GetFollowerAsync(followerUserName, followerHost);
+
+                if (follower != null && follower.Followings.Count + 1 > _instanceSettings.MaxFollowsPerUser)
+                {
+                    return await SendRejectFollowAsync(activity, followerHost);
+                }
+            }
+
             // Validate User Protected
             var user = _twitterUserService.GetUser(twitterUser);
             if (!user.Protected)
diff --git a/src/BirdsiteLive/appsettings.json b/src/BirdsiteLive/appsettings.json
index 42d292c..368c02d 100644
--- a/src/BirdsiteLive/appsettings.json
+++ b/src/BirdsiteLive/appsettings.json
@@ -25,7 +25,8 @@
     "TwitterDomain": "twitter.com",
     "TwitterDomainLabel": "",
     "InfoBanner": "",
-    "ShowAboutInstanceOnProfiles": true
+    "ShowAboutInstanceOnProfiles": true,
+    "MaxFollowsPerUser": 0
   },
   "Db": {
     "Type": "postgres",
@@ -44,4 +45,4 @@
     "TwitterAccountsWhiteListing": null,
     "TwitterAccountsBlackListing": null
   }
-}
+}
\ No newline at end of file
