diff options
| author | Thomas Grothe <grothe.tr@gmail.com> | 2026-04-11 17:49:42 -0400 |
|---|---|---|
| committer | Thomas Grothe <grothe.tr@gmail.com> | 2026-04-11 17:49:42 -0400 |
| commit | d4f97aa956be051dd5b9a184557106dc7de112ac (patch) | |
| tree | e4c73c93408b5fbc4cc1b92cabcaceaaebbb1833 /routes | |
| parent | bcac54576d7309ac0471a7be5664c5a4e8d7349e (diff) | |
| parent | 054c19bf65beb43d0dd6137f9bf16cf8ca9f6190 (diff) | |
Merge remote-tracking branch 'origin/main'
Diffstat (limited to 'routes')
| -rwxr-xr-x | routes/web.php | 104 |
1 files changed, 53 insertions, 51 deletions
diff --git a/routes/web.php b/routes/web.php index e32edfa..e2cf097 100755 --- a/routes/web.php +++ b/routes/web.php @@ -1,71 +1,49 @@ <?php use Illuminate\Support\Facades\Route; +use Illuminate\Support\Facades\Storage; use App\Http\Controllers\SiteController; use App\Http\Controllers\ProfileController; use App\Http\Controllers\WritingController; use App\Http\Controllers\LinkController; use App\Http\Controllers\DashboardController; use App\Http\Controllers\PresenceController; +use App\Http\Controllers\AdminController; use Illuminate\Http\Request; Route::get('/', function () { return view('home'); }); -Route::get('/env', [SiteController::class, 'env']); +Route::get('/dq', [SiteController::class, 'duneQuote'])->middleware('throttle:public'); -Route::get('/dq', [SiteController::class, 'duneQuote']); - -//temporarily desabled for security -//Route::post('/f', [SiteController::class, 'uploadFiles']); //TODO later use file resource or FPR service Route::get('/f/{path}', function($path){ + // Sanitize: strip directory traversal, only allow basename + $path = basename($path); $storpath = storage_path('app/public/uploads/' . $path); if (!file_exists($storpath)){ - return response()->json(['error' => 'file not found ' . $storpath]); + abort(404, 'File not found.'); } - $mime = Storage::mimeType($storpath); - - //return response()->json([ - // 'success' => true, - // 'message' => 'file uploaded', - // 'url' => $storpath, - //]); - -// this is how you would return the file which would download the file in browser + $mime = Storage::mimeType('public/uploads/' . $path); return response()->file($storpath, [ 'Content-Type' => $mime, 'Content-Disposition' => 'inline; filename="'.$path.'"' ]); -}); - -/*Route::get('/yt', function () { - return view('youtube'); -});*/ - -// Get video info and direct stream URL (recommended approach) -//Route::post('/yt/info', [YouTubeController::class, 'getVideoInfo']); -// Stream video through Laravel proxy (with range support) -//Route::get('/yt/stream', [YouTubeController::class, 'streamVideo']); -// Stream directly via yt-dlp (not recommended - no seeking support) -//Route::get('/yt/stream-direct', [YouTubeController::class, 'streamDirect']); -// Get available formats for a video -//Route::post('/yt/formats', [YouTubeController::class, 'getFormats']); +})->middleware('throttle:public'); Route::get('/mu/{path}', function($path){ + // Sanitize: strip directory traversal, only allow basename + $path = basename($path); $storpath = storage_path('app/public/band/' . $path); if (!file_exists($storpath)){ - return response()->json(['error' => 'file not found ' . $storpath]); + abort(404, 'File not found.'); } - $mime = Storage::mimeType($storpath); + $mime = Storage::mimeType('public/band/' . $path); return response()->file($storpath, [ 'Content-Type' => $mime, 'Content-Disposition' => 'inline; filename="'.$path.'"' ]); -}); - -Route::resource('w', WritingController::class); -Route::resource('l', LinkController::class); +})->middleware('throttle:public'); // Presence - Public routes Route::get('/me', [PresenceController::class, 'index']); @@ -85,43 +63,62 @@ Route::middleware('auth')->prefix('me')->group(function () { Route::get('/test', [SiteController::class, 'test']); -Route::get('/4', [SiteController::class, 'search4chan']); - -Route::get('/dashboard', function () { - return view('dashboard'); -})->middleware(['auth', 'verified'])->name('dashboard'); - +// Auth-protected routes Route::middleware('auth')->group(function () { Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard'); Route::get('/dashboard/stats', [DashboardController::class, 'statistics'])->name('dashboard.stats'); + Route::resource('l', LinkController::class)->only(['create', 'store', 'edit', 'update', 'destroy']); + Route::resource('w', WritingController::class)->only(['create', 'store', 'edit', 'update', 'destroy']); + Route::resource('f', \App\Http\Controllers\FileController::class)->only(['create', 'store', 'edit', 'update', 'destroy']); +}); + +// Admin-only routes +Route::middleware(['auth', 'throttle:admin', \App\Http\Middleware\Admin::class])->group(function () { + Route::get('/admin', [AdminController::class, 'index'])->name('admin'); + Route::post('/l/import', [LinkController::class, 'import'])->name('l.import'); + Route::get('/env', [SiteController::class, 'env']); + Route::get('/4', [SiteController::class, 'search4chan']); }); +// Public read-only routes +Route::resource('w', WritingController::class)->only(['index', 'show']); +Route::resource('l', LinkController::class)->only(['index', 'show']); +Route::resource('f', \App\Http\Controllers\FileController::class)->only(['index', 'show']); + +// Session updates - auth required, whitelisted keys only Route::post('/update-session', function(Request $req){ if ($req->input('key') && $req->input('value')){ + $allowed = ['theme', 'locale', 'sidebar']; + if (!in_array($req->input('key'), $allowed)) { + abort(403, 'Session key not allowed.'); + } session()->put($req->input('key'), $req->input('value')); return redirect()->back(); } -}); +})->middleware('auth'); require __DIR__.'/auth.php'; -Route::get('/toy/{v}', function($v){ +Route::get('/toy/{v?}', function($v = null){ if (is_null($v)){ return view('toys.index'); } + // Only allow alphanumeric and hyphens + if (!preg_match('/^[a-zA-Z0-9\-]+$/', $v)) { + abort(404); + } + if (!view()->exists('toys.'.$v)) { + abort(404); + } return view('toys.'.$v); }); -// Audio stream endpoint - streams from ffmpeg +// Audio stream - requires auth to prevent abuse Route::get('/stream/audio', function() { - // Configure ffmpeg command to output mp3 stream - // Example: stream from microphone or other source - //$command = 'ffmpeg -re -i /dev/null -f mp3 -'; - //$command = 'ffmpeg -re -f pulse -i defaul -f mp3 -'; - $command = 'ffmpeg -re -i http://somafm.com/vaporwaves.pls :-f mp3 -'; + $command = 'ffmpeg -re -i http://somafm.com/vaporwaves.pls -f mp3 -'; return response()->stream(function() use ($command) { $handle = popen($command, 'r'); @@ -141,12 +138,17 @@ Route::get('/stream/audio', function() { 'Cache-Control' => 'no-cache', 'X-Accel-Buffering' => 'no' ]); -}); +})->middleware(['auth', 'throttle:expensive']); Route::get('/newtab', function() { return view('newtab'); }); +// Catch-all: only allow specific whitelisted views Route::get('/{v}', function($v){ + $allowed = ['notes', 'kyanite', 'marked', 'v']; + if (!in_array($v, $allowed)) { + abort(404); + } return view($v); -}); +})->where('v', '[a-zA-Z0-9\-]+'); |
