diff --git a/CHANGELOG.md b/CHANGELOG.md index 81e36c387..d556e9a20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ - Updated moderation api, invalidate profile embed. ([b2501bfc](https://github.com/pixelfed/pixelfed/commit/b2501bfc)) - Updated Nodeinfo util, use last_active_at for monthly active user count. ([d200c12c](https://github.com/pixelfed/pixelfed/commit/d200c12c)) - Updated PhotoPresenter, add width and height to images. ([3f8202e2](https://github.com/pixelfed/pixelfed/commit/3f8202e2)) +- Updated Compose Apis, refactor rate limits. ([42375b3d](https://github.com/pixelfed/pixelfed/commit/42375b3d)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.10.10 (2021-01-28)](https://github.com/pixelfed/pixelfed/compare/v0.10.9...v0.10.10) diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index 894c9563c..7cda7beed 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -1043,6 +1043,15 @@ class ApiV1Controller extends Controller return []; } + $limitKey = 'compose:rate-limit:media-upload:' . $user->id; + $limitTtl = now()->addMinutes(15); + $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { + $dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count(); + + return $dailyLimit >= 250; + }); + abort_if($limitReached == true, 429); + $profile = $user->profile; if(config('pixelfed.enforce_account_limit') == true) { @@ -1097,6 +1106,7 @@ class ApiV1Controller extends Controller break; } + Cache::forget($limitKey); $resource = new Fractal\Resource\Item($media, new MediaTransformer()); $res = $this->fractal->createData($resource)->toArray(); $res['preview_url'] = $media->url(). '?cb=1&_v=' . time(); @@ -1753,6 +1763,20 @@ class ApiV1Controller extends Controller $in_reply_to_id = $request->input('in_reply_to_id'); $user = $request->user(); + $limitKey = 'compose:rate-limit:store:' . $user->id; + $limitTtl = now()->addMinutes(15); + $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { + $dailyLimit = Status::whereProfileId($user->profile_id) + ->whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->where('created_at', '>', now()->subDays(1)) + ->count(); + + return $dailyLimit >= 100; + }); + + abort_if($limitReached == true, 429); + $visibility = $profile->is_private ? 'private' : ( $profile->unlisted == true && $request->input('visibility', 'public') == 'public' ? @@ -1826,6 +1850,8 @@ class ApiV1Controller extends Controller Cache::forget('_api:statuses:recent_9:'.$user->profile_id); Cache::forget('profile:status_count:'.$user->profile_id); Cache::forget($user->storageUsedKey()); + Cache::forget('profile:embed:' . $status->profile_id); + Cache::forget($limitKey); $resource = new Fractal\Resource\Item($status, new StatusTransformer()); $res = $this->fractal->createData($resource)->toArray(); diff --git a/app/Http/Controllers/ComposeController.php b/app/Http/Controllers/ComposeController.php index 001fc66c1..35bba5c9a 100644 --- a/app/Http/Controllers/ComposeController.php +++ b/app/Http/Controllers/ComposeController.php @@ -81,6 +81,16 @@ class ComposeController extends Controller $user = Auth::user(); $profile = $user->profile; + $limitKey = 'compose:rate-limit:media-upload:' . $user->id; + $limitTtl = now()->addMinutes(15); + $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { + $dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count(); + + return $dailyLimit >= 250; + }); + + abort_if($limitReached == true, 429); + if(config('pixelfed.enforce_account_limit') == true) { $size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) { return Media::whereUserId($user->id)->sum('size') / 1000; @@ -138,6 +148,7 @@ class ComposeController extends Controller break; } + Cache::forget($limitKey); $resource = new Fractal\Resource\Item($media, new MediaTransformer()); $res = $this->fractal->createData($resource)->toArray(); $res['preview_url'] = $preview_url; @@ -160,6 +171,16 @@ class ComposeController extends Controller $user = Auth::user(); + $limitKey = 'compose:rate-limit:media-updates:' . $user->id; + $limitTtl = now()->addMinutes(15); + $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { + $dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count(); + + return $dailyLimit >= 500; + }); + + abort_if($limitReached == true, 429); + $photo = $request->file('file'); $id = $request->input('id'); @@ -179,6 +200,7 @@ class ComposeController extends Controller 'url' => $media->url() . '?v=' . time() ]; ImageOptimize::dispatch($media); + Cache::forget($limitKey); return $res; } @@ -402,6 +424,21 @@ class ComposeController extends Controller $user = Auth::user(); $profile = $user->profile; + + $limitKey = 'compose:rate-limit:store:' . $user->id; + $limitTtl = now()->addMinutes(15); + $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { + $dailyLimit = Status::whereProfileId($user->profile_id) + ->whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->where('created_at', '>', now()->subDays(1)) + ->count(); + + return $dailyLimit >= 100; + }); + + abort_if($limitReached == true, 429); + $visibility = $request->input('visibility'); $medias = $request->input('media'); $attachments = []; @@ -495,6 +532,7 @@ class ComposeController extends Controller Cache::forget('status:transformer:media:attachments:'.$status->id); Cache::forget($user->storageUsedKey()); Cache::forget('profile:embed:' . $status->profile_id); + Cache::forget($limitKey); return $status->url(); } diff --git a/public/js/compose.js b/public/js/compose.js index 2eea9e6e7..f8aebda6d 100644 Binary files a/public/js/compose.js and b/public/js/compose.js differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index ef93cf5dd..ca9caa6e0 100644 Binary files a/public/mix-manifest.json and b/public/mix-manifest.json differ diff --git a/resources/assets/js/components/ComposeModal.vue b/resources/assets/js/components/ComposeModal.vue index f700f0373..a98028efe 100644 --- a/resources/assets/js/components/ComposeModal.vue +++ b/resources/assets/js/components/ComposeModal.vue @@ -818,6 +818,13 @@ export default { self.page = 2; break; + case 429: + self.uploading = false; + io.value = null; + swal('Limit Reached', 'You can upload up to 250 photos or videos per day and you\'ve reached that limit. Please try again later.', 'error'); + self.page = 2; + break; + default: self.uploading = false; io.value = null; diff --git a/routes/api.php b/routes/api.php index 5bb8144fb..d6d5a6434 100644 --- a/routes/api.php +++ b/routes/api.php @@ -68,7 +68,7 @@ Route::group(['prefix' => 'api'], function() use($middleware) { Route::post('statuses/{id}/unbookmark', 'Api\ApiV1Controller@unbookmarkStatus')->middleware($middleware); Route::delete('statuses/{id}', 'Api\ApiV1Controller@statusDelete')->middleware($middleware); Route::get('statuses/{id}', 'Api\ApiV1Controller@statusById')->middleware($middleware); - Route::post('statuses', 'Api\ApiV1Controller@statusCreate')->middleware($middleware)->middleware('throttle:maxPostsPerHour,60')->middleware('throttle:maxPostsPerDay,1440'); + Route::post('statuses', 'Api\ApiV1Controller@statusCreate')->middleware($middleware); Route::get('timelines/home', 'Api\ApiV1Controller@timelineHome')->middleware($middleware); diff --git a/routes/web.php b/routes/web.php index 91b088a90..235001a72 100644 --- a/routes/web.php +++ b/routes/web.php @@ -106,19 +106,14 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::group(['prefix' => 'compose'], function() { Route::group(['prefix' => 'v0'], function() { Route::post('/media/upload', 'ComposeController@mediaUpload'); - Route::post('/media/update', 'ComposeController@mediaUpdate') - ->middleware('throttle:maxComposeMediaUpdatesPerHour,60') - ->middleware('throttle:maxComposeMediaUpdatesPerDay,1440') - ->middleware('throttle:maxComposeMediaUpdatesPerMonth,43800'); + Route::post('/media/update', 'ComposeController@mediaUpdate'); Route::delete('/media/delete', 'ComposeController@mediaDelete'); Route::get('/search/tag', 'ComposeController@searchTag'); Route::get('/search/location', 'ComposeController@searchLocation'); Route::get('/search/mention', 'ComposeController@searchMentionAutocomplete'); Route::get('/search/hashtag', 'ComposeController@searchHashtagAutocomplete'); - Route::post('/publish', 'ComposeController@store') - ->middleware('throttle:maxPostsPerHour,60') - ->middleware('throttle:maxPostsPerDay,1440'); + Route::post('/publish', 'ComposeController@store'); Route::post('/publish/text', 'ComposeController@storeText'); Route::get('/media/processing', 'ComposeController@mediaProcessingCheck'); });