github.com/cozy/cozy-stack@v0.0.0-20240327093429-939e4a21320e/pkg/limits/rate_limiting.go (about) 1 package limits 2 3 import ( 4 "errors" 5 "time" 6 7 "github.com/cozy/cozy-stack/pkg/prefixer" 8 "github.com/redis/go-redis/v9" 9 ) 10 11 // CounterType os an enum for the type of counters used by rate-limiting. 12 type CounterType int 13 14 // ErrRateLimitReached is the error returned when we were under the limit 15 // before the check, and reach the limit. 16 var ErrRateLimitReached = errors.New("Rate limit reached") 17 18 // ErrRateLimitExceeded is the error returned when the limit was already 19 // reached before the check. 20 var ErrRateLimitExceeded = errors.New("Rate limit exceeded") 21 22 const ( 23 // AuthType is used for counting the number of login attempts. 24 AuthType CounterType = iota 25 // TwoFactorGenerationType is used for counting the number of times a 2FA 26 // is generated. 27 TwoFactorGenerationType 28 // TwoFactorType is used for counting the number of 2FA attempts. 29 TwoFactorType 30 // OAuthClientType is used for counting the number of OAuth clients. 31 // creations/updates. 32 OAuthClientType 33 // SharingInviteType is used for counting the number of sharing invitations 34 // sent to a given instance. 35 SharingInviteType 36 // SharingPublicLinkType is used for counting the number of public sharing 37 // link consultations 38 SharingPublicLinkType 39 // JobThumbnailType is used for counting the number of thumbnail jobs 40 // executed by an instance 41 JobThumbnailType 42 // JobShareTrackType is used for counting the number of updates of the 43 // io.cozy.shared database 44 JobShareTrackType 45 // JobShareReplicateType is used for counting the number of replications 46 JobShareReplicateType 47 // JobShareUploadType is used for counting the file uploads 48 JobShareUploadType 49 // JobKonnectorType is used for counting the number of konnector executions 50 JobKonnectorType 51 // JobZipType is used for cozies exports 52 JobZipType 53 // JobSendMailType is used for mail sending 54 JobSendMailType 55 // JobServiceType is used for generic services 56 // Ex: categorization or matching for banking 57 JobServiceType 58 // JobNotificationType is used for mobile notifications pushing 59 JobNotificationType 60 // SendHintByMail is used for sending the password hint by email 61 SendHintByMail 62 // JobNotesPersistType is used for saving notes to the VFS 63 JobNotesPersistType 64 // JobClientType is used for the jobs associated to a @client trigger 65 JobClientType 66 // ExportType is used for creating an export of the data 67 ExportType 68 // WebhookTriggerType is used for calling a webhook trigger 69 WebhookTriggerType 70 // JobCleanClientType is used for cleaning unused OAuth clients 71 JobCleanClientType 72 // ConfirmFlagshipType is used when the user is asked to manually certify 73 // that an OAuth client is the flagship app. 74 ConfirmFlagshipType 75 // MagicLinkType is used when sending emails with a magic link that can 76 // authenticate the user into a Cozy 77 MagicLinkType 78 // ResendOnboardingMailType is used for resending the onboarding link by email 79 ResendOnboardingMailType 80 ) 81 82 type counterConfig struct { 83 Prefix string 84 Limit int64 85 Period time.Duration 86 } 87 88 var configs = []counterConfig{ 89 // AuthType 90 { 91 Prefix: "auth", 92 Limit: 1000, 93 Period: 1 * time.Hour, 94 }, 95 // TwoFactorGenerationType 96 { 97 Prefix: "two-factor-generation", 98 Limit: 20, 99 Period: 1 * time.Hour, 100 }, 101 // TwoFactorType 102 { 103 Prefix: "two-factor", 104 Limit: 10, 105 Period: 5 * time.Minute, 106 }, 107 // OAuthClientType 108 { 109 Prefix: "oauth-client", 110 Limit: 50, 111 Period: 1 * time.Hour, 112 }, 113 // SharingInviteType 114 { 115 Prefix: "sharing-invite", 116 Limit: 20, 117 Period: 1 * time.Hour, 118 }, 119 // SharingPublicLink 120 { 121 Prefix: "sharing-public-link", 122 Limit: 2000, 123 Period: 1 * time.Hour, 124 }, 125 // JobThumbnail 126 { 127 Prefix: "job-thumbnail", 128 Limit: 20000, 129 Period: 1 * time.Hour, 130 }, 131 // JobShareTrack 132 { 133 Prefix: "job-share-track", 134 Limit: 20000, 135 Period: 1 * time.Hour, 136 }, 137 // JobShareReplicate 138 { 139 Prefix: "job-share-replicate", 140 Limit: 2000, 141 Period: 1 * time.Hour, 142 }, 143 // JobShareUpload 144 { 145 Prefix: "job-share-upload", 146 Limit: 1000, 147 Period: 1 * time.Hour, 148 }, 149 // JobKonnector 150 { 151 Prefix: "job-konnector", 152 Limit: 100, 153 Period: 1 * time.Hour, 154 }, 155 // JobZip 156 { 157 Prefix: "job-zip", 158 Limit: 100, 159 Period: 1 * time.Hour, 160 }, 161 // JobSendMail 162 { 163 Prefix: "job-sendmail", 164 Limit: 200, 165 Period: 1 * time.Hour, 166 }, 167 // JobService 168 { 169 Prefix: "job-service", 170 Limit: 200, 171 Period: 1 * time.Hour, 172 }, 173 // JobNotification 174 { 175 Prefix: "job-push", 176 Limit: 30, 177 Period: 1 * time.Hour, 178 }, 179 // SendHintByMail 180 { 181 Prefix: "send-hint", 182 Limit: 2, 183 Period: 1 * time.Hour, 184 }, 185 // JobNotesPersistType 186 { 187 Prefix: "job-notes-persist", 188 Limit: 100, 189 Period: 1 * time.Hour, 190 }, 191 // JobClientType 192 { 193 Prefix: "job-client", 194 Limit: 100, 195 Period: 1 * time.Hour, 196 }, 197 // ExportType 198 { 199 Prefix: "export", 200 Limit: 5, 201 Period: 24 * time.Hour, 202 }, 203 // WebhookTriggerType 204 { 205 Prefix: "webhook-trigger", 206 Limit: 30, 207 Period: 1 * time.Hour, 208 }, 209 // JobCleanClientType 210 { 211 Prefix: "job-clean-clients", 212 Limit: 100, 213 Period: 1 * time.Hour, 214 }, 215 // ConfirmFlagshipType 216 { 217 Prefix: "confirm-flagship", 218 Limit: 30, 219 Period: 1 * time.Hour, 220 }, 221 // MagicLinkType 222 { 223 Prefix: "magic-link", 224 Limit: 30, 225 Period: 1 * time.Hour, 226 }, 227 // ResendOnboardingMailType 228 { 229 Prefix: "resend-onboarding-mail", 230 Limit: 2, 231 Period: 1 * time.Hour, 232 }, 233 } 234 235 // Counter is an interface for counting number of attempts that can be used to 236 // rate limit the number of logins and 2FA tries, and thus block bruteforce 237 // attacks. 238 type Counter interface { 239 Increment(key string, timeLimit time.Duration) (int64, error) 240 Reset(key string) error 241 } 242 243 // RateLimiter allow to rate limite the access to some resource. 244 type RateLimiter struct { 245 counter Counter 246 } 247 248 // NewRateLimiter instantiate a new [RateLimiter]. 249 // 250 // The backend selection is done based on the `client` argument. If a client is 251 // given, the redis backend is chosen, if nil is provided the inmemory backend would 252 // be chosen. 253 func NewRateLimiter(client redis.UniversalClient) *RateLimiter { 254 if client == nil { 255 return &RateLimiter{NewInMemory()} 256 } 257 258 return &RateLimiter{NewRedis(client)} 259 } 260 261 // CheckRateLimit returns an error if the counter for the given type and 262 // instance has reached the limit. 263 func (r *RateLimiter) CheckRateLimit(p prefixer.Prefixer, ct CounterType) error { 264 return r.CheckRateLimitKey(p.DomainName(), ct) 265 } 266 267 // CheckRateLimitKey allows to check the rate-limit for a key 268 func (r *RateLimiter) CheckRateLimitKey(customKey string, ct CounterType) error { 269 cfg := configs[ct] 270 key := cfg.Prefix + ":" + customKey 271 272 val, err := r.counter.Increment(key, cfg.Period) 273 if err != nil { 274 return err 275 } 276 277 // The first time we reach the limit, we provide a specific error message. 278 // This allows to log a warning only once if needed. 279 if val == cfg.Limit+1 { 280 return ErrRateLimitReached 281 } 282 283 if val > cfg.Limit { 284 return ErrRateLimitExceeded 285 } 286 287 return nil 288 } 289 290 // ResetCounter sets again to zero the counter for the given type and instance. 291 func (r *RateLimiter) ResetCounter(p prefixer.Prefixer, ct CounterType) { 292 cfg := configs[ct] 293 key := cfg.Prefix + ":" + p.DomainName() 294 295 _ = r.counter.Reset(key) 296 } 297 298 // IsLimitReachedOrExceeded return true if the limit has been reached or 299 // exceeded, false otherwise. 300 func IsLimitReachedOrExceeded(err error) bool { 301 return errors.Is(err, ErrRateLimitReached) || errors.Is(err, ErrRateLimitExceeded) 302 } 303 304 // GetMaximumLimit returns the limit of a CounterType 305 func GetMaximumLimit(ct CounterType) int64 { 306 return configs[ct].Limit 307 } 308 309 // SetMaximumLimit sets a new limit for a CounterType 310 func SetMaximumLimit(ct CounterType, newLimit int64) { 311 configs[ct].Limit = newLimit 312 }