feat: add captcha-backed forum security hardening

This commit is contained in:
2026-03-17 16:06:28 +01:00
parent 980a15f66e
commit b3fc889452
40 changed files with 2849 additions and 108 deletions

View File

@@ -11,20 +11,20 @@ Route::middleware(['web', 'auth'])->prefix('dashboard')->name('api.dashboard.')-
});
Route::middleware(['web', 'auth', 'creator.access'])->prefix('stories')->name('api.stories.')->group(function () {
Route::post('create', [\App\Http\Controllers\StoryController::class, 'apiCreate'])->name('create');
Route::put('update', [\App\Http\Controllers\StoryController::class, 'apiUpdate'])->name('update');
Route::post('autosave', [\App\Http\Controllers\StoryController::class, 'apiAutosave'])->name('autosave');
Route::post('create', [\App\Http\Controllers\StoryController::class, 'apiCreate'])->middleware('forum.bot.protection:api_write')->name('create');
Route::put('update', [\App\Http\Controllers\StoryController::class, 'apiUpdate'])->middleware('forum.bot.protection:api_write')->name('update');
Route::post('autosave', [\App\Http\Controllers\StoryController::class, 'apiAutosave'])->middleware('forum.bot.protection:api_write')->name('autosave');
});
Route::middleware(['web', 'auth', 'creator.access'])->prefix('story')->name('api.story.')->group(function () {
Route::post('upload-image', [\App\Http\Controllers\StoryController::class, 'apiUploadImage'])->name('upload-image');
Route::post('upload-image', [\App\Http\Controllers\StoryController::class, 'apiUploadImage'])->middleware('forum.bot.protection:api_write')->name('upload-image');
Route::get('artworks', [\App\Http\Controllers\StoryController::class, 'apiArtworks'])->name('artworks');
});
Route::middleware(['web', 'auth', 'normalize.username'])->prefix('profile/cover')->name('api.profile.cover.')->group(function () {
Route::post('upload', [\App\Http\Controllers\User\ProfileCoverController::class, 'upload'])->middleware('throttle:20,1')->name('upload');
Route::post('position', [\App\Http\Controllers\User\ProfileCoverController::class, 'updatePosition'])->middleware('throttle:30,1')->name('position');
Route::delete('/', [\App\Http\Controllers\User\ProfileCoverController::class, 'destroy'])->middleware('throttle:20,1')->name('destroy');
Route::post('upload', [\App\Http\Controllers\User\ProfileCoverController::class, 'upload'])->middleware(['throttle:20,1', 'forum.bot.protection:profile_update'])->name('upload');
Route::post('position', [\App\Http\Controllers\User\ProfileCoverController::class, 'updatePosition'])->middleware(['throttle:30,1', 'forum.bot.protection:profile_update'])->name('position');
Route::delete('/', [\App\Http\Controllers\User\ProfileCoverController::class, 'destroy'])->middleware(['throttle:20,1', 'forum.bot.protection:profile_update'])->name('destroy');
});
// ── Per-artwork signal tracking (public) ────────────────────────────────────
@@ -38,14 +38,20 @@ Route::middleware(['web', 'throttle:300,1'])
Route::middleware(['web', 'throttle:5,10'])
->post('art/{id}/view', \App\Http\Controllers\Api\ArtworkViewController::class)
->middleware('forum.bot.protection:api_write')
->whereNumber('id')
->name('api.art.view');
Route::middleware(['web', 'throttle:10,1'])
->post('art/{id}/download', \App\Http\Controllers\Api\ArtworkDownloadController::class)
->middleware('forum.bot.protection:api_write')
->whereNumber('id')
->name('api.art.download');
Route::middleware(['web', 'throttle:reactions-read'])
->get('community/activity', [\App\Http\Controllers\Api\CommunityActivityController::class, 'index'])
->name('api.community.activity');
// ── Ranking lists (public, throttled, Redis-cached) ─────────────────────────
// GET /api/rank/global?type=trending|new_hot|best
// GET /api/rank/category/{id}?type=trending|new_hot|best
@@ -136,24 +142,25 @@ Route::middleware(['throttle:60,1'])
Route::middleware(['web', 'auth', 'normalize.username'])->prefix('artworks')->name('api.artworks.')->group(function () {
Route::post('/', [\App\Http\Controllers\Api\ArtworkController::class, 'store'])
->middleware('forum.bot.protection:api_write')
->name('store');
});
Route::middleware(['web', 'auth', 'normalize.username'])->prefix('uploads')->name('api.uploads.')->group(function () {
Route::post('init', [\App\Http\Controllers\Api\UploadController::class, 'init'])
->middleware('throttle:uploads-init')
->middleware(['throttle:uploads-init', 'forum.bot.protection:api_write'])
->name('init');
Route::post('preload', [\App\Http\Controllers\Api\UploadController::class, 'preload'])
->middleware('throttle:uploads-init')
->middleware(['throttle:uploads-init', 'forum.bot.protection:api_write'])
->name('preload');
Route::post('{id}/autosave', [\App\Http\Controllers\Api\UploadController::class, 'autosave'])
->middleware('throttle:uploads-finish')
->middleware(['throttle:uploads-finish', 'forum.bot.protection:api_write'])
->name('autosave');
Route::post('{id}/publish', [\App\Http\Controllers\Api\UploadController::class, 'publish'])
->middleware('throttle:uploads-finish')
->middleware(['throttle:uploads-finish', 'forum.bot.protection:api_write'])
->name('publish');
Route::get('{id}/status', [\App\Http\Controllers\Api\UploadController::class, 'processingStatus'])
@@ -161,15 +168,15 @@ Route::middleware(['web', 'auth', 'normalize.username'])->prefix('uploads')->nam
->name('processing-status');
Route::post('chunk', [\App\Http\Controllers\Api\UploadController::class, 'chunk'])
->middleware('throttle:uploads-init')
->middleware(['throttle:uploads-init', 'forum.bot.protection:api_write'])
->name('chunk');
Route::post('finish', [\App\Http\Controllers\Api\UploadController::class, 'finish'])
->middleware('throttle:uploads-finish')
->middleware(['throttle:uploads-finish', 'forum.bot.protection:api_write'])
->name('finish');
Route::post('cancel', [\App\Http\Controllers\Api\UploadController::class, 'cancel'])
->middleware('throttle:uploads-finish')
->middleware(['throttle:uploads-finish', 'forum.bot.protection:api_write'])
->name('cancel');
Route::get('status/{id}', [\App\Http\Controllers\Api\UploadController::class, 'status'])
@@ -204,6 +211,9 @@ Route::middleware(['web', 'auth', 'admin.moderation'])->prefix('admin/reports')-
Route::get('feed-performance', [\App\Http\Controllers\Api\Admin\FeedPerformanceReportController::class, 'index'])
->name('feed-performance');
Route::get('tags', [\App\Http\Controllers\Api\Admin\TagInteractionReportController::class, 'index'])
->name('tags');
});
Route::middleware(['web', 'auth', 'admin.moderation'])->prefix('admin/usernames')->name('api.admin.usernames.')->group(function () {
@@ -223,13 +233,17 @@ Route::post('analytics/similar-artworks', [\App\Http\Controllers\Api\SimilarArtw
->middleware('throttle:uploads-status')
->name('api.analytics.similar-artworks.store');
Route::middleware(['web'])->post('analytics/tags', [\App\Http\Controllers\Api\TagInteractionAnalyticsController::class, 'store'])
->middleware(['throttle:uploads-status', 'forum.bot.protection:api_write'])
->name('api.analytics.tags.store');
Route::middleware(['web', 'auth'])->post('analytics/feed', [\App\Http\Controllers\Api\FeedAnalyticsController::class, 'store'])
->middleware('throttle:uploads-status')
->middleware(['throttle:uploads-status', 'forum.bot.protection:api_write'])
->name('api.analytics.feed.store');
Route::middleware(['web', 'auth', 'normalize.username'])->prefix('discovery')->name('api.discovery.')->group(function () {
Route::post('events', [\App\Http\Controllers\Api\DiscoveryEventController::class, 'store'])
->middleware('throttle:uploads-status')
->middleware(['throttle:uploads-status', 'forum.bot.protection:api_write'])
->name('events.store');
});