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

@@ -273,18 +273,18 @@ Route::middleware(['auth', 'normalize.username', 'ensure.onboarding.complete'])-
Route::match(['post', 'put'], '/profile/password', [ProfileController::class, 'password'])->name('profile.password');
Route::post('/avatar/upload', [AvatarController::class, 'upload'])->middleware('throttle:20,1')->name('avatar.upload');
Route::post('/settings/profile/update', [ProfileController::class, 'updateProfileSection'])->name('settings.profile.update');
Route::post('/settings/account/username', [ProfileController::class, 'updateUsername'])->name('settings.account.username');
Route::post('/settings/account/update', [ProfileController::class, 'updateAccountSection'])->name('settings.account.update');
Route::post('/settings/profile/update', [ProfileController::class, 'updateProfileSection'])->middleware('forum.bot.protection:profile_update')->name('settings.profile.update');
Route::post('/settings/account/username', [ProfileController::class, 'updateUsername'])->middleware('forum.bot.protection:profile_update')->name('settings.account.username');
Route::post('/settings/account/update', [ProfileController::class, 'updateAccountSection'])->middleware('forum.bot.protection:profile_update')->name('settings.account.update');
Route::post('/settings/email/request', [ProfileController::class, 'requestEmailChange'])
->middleware('throttle:email-change-request')
->name('settings.email.request');
Route::post('/settings/email/verify', [ProfileController::class, 'verifyEmailChange'])
->middleware('throttle:10,1')
->name('settings.email.verify');
Route::post('/settings/personal/update', [ProfileController::class, 'updatePersonalSection'])->name('settings.personal.update');
Route::post('/settings/notifications/update', [ProfileController::class, 'updateNotificationsSection'])->name('settings.notifications.update');
Route::post('/settings/security/password', [ProfileController::class, 'updateSecurityPassword'])->name('settings.security.password');
Route::post('/settings/personal/update', [ProfileController::class, 'updatePersonalSection'])->middleware('forum.bot.protection:profile_update')->name('settings.personal.update');
Route::post('/settings/notifications/update', [ProfileController::class, 'updateNotificationsSection'])->middleware('forum.bot.protection:profile_update')->name('settings.notifications.update');
Route::post('/settings/security/password', [ProfileController::class, 'updateSecurityPassword'])->middleware('forum.bot.protection:profile_update')->name('settings.security.password');
});
// ── UPLOAD ────────────────────────────────────────────────────────────────────
@@ -377,6 +377,10 @@ Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function ()
->middleware('admin.moderation')
->name('reports.queue');
Route::get('reports/tags', [\App\Http\Controllers\Admin\TagInteractionReportController::class, 'index'])
->middleware('admin.moderation')
->name('reports.tags');
Route::middleware('admin.moderation')->prefix('early-growth')->name('early-growth.')->group(function () {
Route::get('/', [\App\Http\Controllers\Admin\EarlyGrowthAdminController::class, 'index'])->name('index');
Route::delete('/cache', [\App\Http\Controllers\Admin\EarlyGrowthAdminController::class, 'flushCache'])->name('cache.flush');
@@ -411,6 +415,9 @@ Route::middleware(['auth', 'ensure.onboarding.complete'])->prefix('messages')->n
});
// ── COMMUNITY ACTIVITY ────────────────────────────────────────────────────────
Route::match(['get', 'post'], '/community/chat', [\App\Http\Controllers\Community\ChatController::class, 'index'])
->name('community.chat');
Route::get('/community/activity', [\App\Http\Controllers\Web\CommunityActivityController::class, 'index'])
->name('community.activity');
@@ -430,9 +437,11 @@ Route::get('/feed/search', [\App\Http\Controllers\Web\Posts\SearchFeedController
->name('feed.search');
// ── CONTENT BROWSER (artwork / category universal router) ─────────────────────
// Bind the artwork route parameter to the Artwork model by slug.
// Bind the artwork route parameter by slug when possible, but don't hard-fail.
// Some URLs that match this shape are actually nested category paths such as
// /skins/audio/blazemedia-pro, which should fall through to category handling.
Route::bind('artwork', function ($value) {
return Artwork::where('slug', $value)->firstOrFail();
return Artwork::where('slug', $value)->first();
});
Route::get('/{contentTypeSlug}/{categoryPath}/{artwork}', [BrowseGalleryController::class, 'showArtwork'])