summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/Http/Controllers/DashboardController.php27
-rw-r--r--app/Http/Controllers/FileController.php9
-rw-r--r--app/Http/Controllers/LinkController.php4
-rw-r--r--app/Http/Controllers/WritingController.php6
-rw-r--r--app/Http/Middleware/Admin.php21
-rw-r--r--app/Policies/FilePolicy.php49
-rw-r--r--app/Policies/LinkPolicy.php49
-rw-r--r--app/Policies/WritingPolicy.php49
-rwxr-xr-xapp/Providers/AppServiceProvider.php18
9 files changed, 218 insertions, 14 deletions
diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php
index 7335629..c104e77 100644
--- a/app/Http/Controllers/DashboardController.php
+++ b/app/Http/Controllers/DashboardController.php
@@ -16,10 +16,29 @@ class DashboardController extends Controller
{
$user = Auth::user();
- // You can add any additional data preparation here
- // before passing to the view
-
- return view('dashboard');
+ // Storage stats
+ $usedStorage = $user->getStorageUsed();
+ $totalStorage = $user->storage_quota;
+ $storagePercent = $totalStorage > 0 ? min(100, round(($usedStorage / $totalStorage) * 100)) : 0;
+
+ // File stats
+ $fileCount = 0; // TODO: update when DB schema supports user->files()
+ $recentFiles = collect();
+
+ // Writing stats
+ $writingCount = $user->writings()->count();
+ $recentWritings = $user->writings()->orderBy('created_at', 'desc')->take(3)->get();
+
+ return view('dashboard', compact(
+ 'user',
+ 'usedStorage',
+ 'totalStorage',
+ 'storagePercent',
+ 'fileCount',
+ 'recentFiles',
+ 'writingCount',
+ 'recentWritings',
+ ));
}
/**
diff --git a/app/Http/Controllers/FileController.php b/app/Http/Controllers/FileController.php
index 5a4ed40..70d07ef 100644
--- a/app/Http/Controllers/FileController.php
+++ b/app/Http/Controllers/FileController.php
@@ -145,7 +145,8 @@ class FileController extends Controller
public function addTags(Request $request, $fileId)
{
$file = File::findOrFail($fileId);
-
+ $this->authorize('update', $file);
+
$validated = $request->validate([
'tags' => 'required|string'
]);
@@ -169,6 +170,8 @@ class FileController extends Controller
public function removeTag(Request $request, $fileId, $tagId)
{
$file = File::findOrFail($fileId);
+ $this->authorize('update', $file);
+
$tag = Tag::findOrFail($tagId);
$file->tags()->detach($tagId);
@@ -196,6 +199,8 @@ class FileController extends Controller
*/
public function update(Request $request, File $file)
{
+ $this->authorize('update', $file);
+
$validated = $request->validate([
'description' => 'nullable|string',
'tags' => 'nullable|string'
@@ -218,6 +223,8 @@ class FileController extends Controller
*/
public function destroy(File $file)
{
+ $this->authorize('delete', $file);
+
// Remove the file from storage
if (Storage::disk('public')->exists($file->path)) {
Storage::disk('public')->delete($file->path);
diff --git a/app/Http/Controllers/LinkController.php b/app/Http/Controllers/LinkController.php
index 9ac44d8..e889489 100644
--- a/app/Http/Controllers/LinkController.php
+++ b/app/Http/Controllers/LinkController.php
@@ -70,6 +70,8 @@ class LinkController extends Controller
*/
public function update(UpdateLinkRequest $request, Link $link)
{
+ $this->authorize('update', $link);
+
$validated = $request->validated();
$link->update($validated);
@@ -82,6 +84,8 @@ class LinkController extends Controller
*/
public function destroy(Link $link)
{
+ $this->authorize('delete', $link);
+
$link->delete();
$redirect = request()->input('_redirect', route('l.index'));
diff --git a/app/Http/Controllers/WritingController.php b/app/Http/Controllers/WritingController.php
index 974852f..67d7b8d 100644
--- a/app/Http/Controllers/WritingController.php
+++ b/app/Http/Controllers/WritingController.php
@@ -66,6 +66,8 @@ class WritingController extends Controller
public function edit($id)
{
$writing = Writing::findOrFail($id);
+ $this->authorize('update', $writing);
+
return view('writings.edit', [
'writing' => $writing
]);
@@ -76,6 +78,8 @@ class WritingController extends Controller
*/
public function update(Request $request, Writing $writing)
{
+ $this->authorize('update', $writing);
+
$validated = $request->validate([
'title' => 'required|min:3|max:255',
'content' => 'required|min:10',
@@ -92,6 +96,8 @@ class WritingController extends Controller
*/
public function destroy(Writing $writing)
{
+ $this->authorize('delete', $writing);
+
$writing->delete();
return redirect()->route('w.index')
diff --git a/app/Http/Middleware/Admin.php b/app/Http/Middleware/Admin.php
index ab70520..65fba8c 100644
--- a/app/Http/Middleware/Admin.php
+++ b/app/Http/Middleware/Admin.php
@@ -6,6 +6,7 @@ use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Notification;
use App\Notifications\AdminAccessNotification;
use Illuminate\Notifications\AnonymousNotifiable;
@@ -21,16 +22,20 @@ class Admin
abort(403, 'Unauthorized');
}
- // Notify admin email of access
+ // Notify admin email of access — throttled to once per 15 minutes per user+IP
$adminEmail = config('app.admin_notify_email');
if ($adminEmail) {
- Notification::route('mail', $adminEmail)->notify(
- new AdminAccessNotification(
- Auth::user()->name,
- $request->ip(),
- $request->header('User-Agent', 'unknown')
- )
- );
+ $cacheKey = 'admin_notify:' . Auth::id() . ':' . $request->ip();
+ if (!Cache::has($cacheKey)) {
+ Notification::route('mail', $adminEmail)->notify(
+ new AdminAccessNotification(
+ Auth::user()->name,
+ $request->ip(),
+ $request->header('User-Agent', 'unknown')
+ )
+ );
+ Cache::put($cacheKey, true, now()->addMinutes(15));
+ }
}
return $next($request);
diff --git a/app/Policies/FilePolicy.php b/app/Policies/FilePolicy.php
new file mode 100644
index 0000000..fe46f8b
--- /dev/null
+++ b/app/Policies/FilePolicy.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Policies;
+
+use App\Models\File;
+use App\Models\User;
+
+class FilePolicy
+{
+ /**
+ * Anyone can view listings.
+ */
+ public function viewAny(?User $user): bool
+ {
+ return true;
+ }
+
+ /**
+ * Anyone can view a single file.
+ */
+ public function view(?User $user, File $file): bool
+ {
+ return true;
+ }
+
+ /**
+ * Any authenticated user can create files.
+ */
+ public function create(User $user): bool
+ {
+ return true;
+ }
+
+ /**
+ * Only the owner or an admin can update.
+ */
+ public function update(User $user, File $file): bool
+ {
+ return $user->id === $file->user_id || $user->isAdmin();
+ }
+
+ /**
+ * Only the owner or an admin can delete.
+ */
+ public function delete(User $user, File $file): bool
+ {
+ return $user->id === $file->user_id || $user->isAdmin();
+ }
+}
diff --git a/app/Policies/LinkPolicy.php b/app/Policies/LinkPolicy.php
new file mode 100644
index 0000000..1eba28f
--- /dev/null
+++ b/app/Policies/LinkPolicy.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Policies;
+
+use App\Models\Link;
+use App\Models\User;
+
+class LinkPolicy
+{
+ /**
+ * Anyone can view listings.
+ */
+ public function viewAny(?User $user): bool
+ {
+ return true;
+ }
+
+ /**
+ * Anyone can view a single link.
+ */
+ public function view(?User $user, Link $link): bool
+ {
+ return true;
+ }
+
+ /**
+ * Any authenticated user can create links.
+ */
+ public function create(User $user): bool
+ {
+ return true;
+ }
+
+ /**
+ * Only the owner or an admin can update.
+ */
+ public function update(User $user, Link $link): bool
+ {
+ return $user->id === $link->user_id || $user->isAdmin();
+ }
+
+ /**
+ * Only the owner or an admin can delete.
+ */
+ public function delete(User $user, Link $link): bool
+ {
+ return $user->id === $link->user_id || $user->isAdmin();
+ }
+}
diff --git a/app/Policies/WritingPolicy.php b/app/Policies/WritingPolicy.php
new file mode 100644
index 0000000..e2bbaab
--- /dev/null
+++ b/app/Policies/WritingPolicy.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Policies;
+
+use App\Models\User;
+use App\Models\Writing;
+
+class WritingPolicy
+{
+ /**
+ * Anyone can view listings.
+ */
+ public function viewAny(?User $user): bool
+ {
+ return true;
+ }
+
+ /**
+ * Anyone can view a single writing.
+ */
+ public function view(?User $user, Writing $writing): bool
+ {
+ return true;
+ }
+
+ /**
+ * Any authenticated user can create writings.
+ */
+ public function create(User $user): bool
+ {
+ return true;
+ }
+
+ /**
+ * Only the owner or an admin can update.
+ */
+ public function update(User $user, Writing $writing): bool
+ {
+ return $user->id === $writing->user_id || $user->isAdmin();
+ }
+
+ /**
+ * Only the owner or an admin can delete.
+ */
+ public function delete(User $user, Writing $writing): bool
+ {
+ return $user->id === $writing->user_id || $user->isAdmin();
+ }
+}
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 452e6b6..8eee884 100755
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -3,6 +3,9 @@
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
+use Illuminate\Cache\RateLimiting\Limit;
+use Illuminate\Support\Facades\RateLimiter;
+use Illuminate\Http\Request;
class AppServiceProvider extends ServiceProvider
{
@@ -19,6 +22,19 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(): void
{
- //
+ // Public routes: 60 requests per minute per IP
+ RateLimiter::for('public', function (Request $request) {
+ return Limit::perMinute(60)->by($request->ip());
+ });
+
+ // Admin routes: 30 requests per minute per authenticated user
+ RateLimiter::for('admin', function (Request $request) {
+ return Limit::perMinute(30)->by($request->user()?->id ?: $request->ip());
+ });
+
+ // Expensive operations (audio stream, file uploads): 5 per minute
+ RateLimiter::for('expensive', function (Request $request) {
+ return Limit::perMinute(5)->by($request->user()?->id ?: $request->ip());
+ });
}
}