diff --git a/app/Attachments/AttachmentWriter.php b/app/Attachments/AttachmentWriter.php
index e68a6ff..560a0b3 100644
--- a/app/Attachments/AttachmentWriter.php
+++ b/app/Attachments/AttachmentWriter.php
@@ -2,7 +2,6 @@
namespace App\Attachments;
-use Illuminate\Http\UploadedFile;
use Livewire\Wireable;
interface AttachmentWriter extends Wireable
diff --git a/app/Auth/Registration/Registrant.php b/app/Auth/Registration/Registrant.php
index 9480304..c82e414 100644
--- a/app/Auth/Registration/Registrant.php
+++ b/app/Auth/Registration/Registrant.php
@@ -7,7 +7,7 @@ class Registrant
public function __construct(
private string $email,
- private bool $isAdmin
+ private int $permissionLevel
) {
}
@@ -16,9 +16,9 @@ public function getEmail(): string
return $this->email;
}
- public function isAdmin(): bool
+ public function getPermissionLevel(): int
{
- return $this->isAdmin;
+ return $this->permissionLevel;
}
}
diff --git a/app/Auth/Registration/RegistrationTokenRepository.php b/app/Auth/Registration/RegistrationTokenRepository.php
index 358e88d..b401a42 100644
--- a/app/Auth/Registration/RegistrationTokenRepository.php
+++ b/app/Auth/Registration/RegistrationTokenRepository.php
@@ -58,7 +58,7 @@ private function createRecord(Registrant $registrant, #[\SensitiveParameter] str
{
return [
'email' => $registrant->getEmail(),
- 'is_admin' => $registrant->isAdmin(),
+ 'permission_level' => $registrant->getPermissionLevel(),
'token' => Hash::make($token),
'created_at' => new Carbon
];
@@ -81,7 +81,7 @@ private function tokenExpired($createdAt)
private function readRecord($record): Registrant
{
- return new Registrant($record['email'], $record['is_admin']);
+ return new Registrant($record['email'], $record['permission_level']);
}
public function deleteExisting(string $email): bool
diff --git a/app/Http/Middleware/AdminAuthorization.php b/app/Http/Middleware/AdminAuthorization.php
new file mode 100644
index 0000000..49bfe82
--- /dev/null
+++ b/app/Http/Middleware/AdminAuthorization.php
@@ -0,0 +1,14 @@
+hasHeader('Authorization');
+ }
+
+ public static function getAuthenticatedUser(Request $request): User|false
+ {
+ $credentials = base64_decode(substr($request->header('Authorization'), 6));
+ list($username, $password) = explode(':', $credentials);
+ $user = User::where('email', $username)->first();
+ if (isset($user) and Hash::check($password, $user->password)) {
+ return $user;
+ }
+ return false;
+ }
+
+}
diff --git a/app/Http/Middleware/IsAdminMiddleware.php b/app/Http/Middleware/IsAdminMiddleware.php
deleted file mode 100644
index c91d006..0000000
--- a/app/Http/Middleware/IsAdminMiddleware.php
+++ /dev/null
@@ -1,23 +0,0 @@
-check() && auth()->user()->is_admin) {
- return $next($request);
- }
- abort(Response::HTTP_FORBIDDEN);
- }
-}
diff --git a/app/Http/Middleware/MavenEditorAuthorization.php b/app/Http/Middleware/MavenEditorAuthorization.php
new file mode 100644
index 0000000..0fabf3d
--- /dev/null
+++ b/app/Http/Middleware/MavenEditorAuthorization.php
@@ -0,0 +1,14 @@
+check()) {
+ $user = auth()->user();
+ } else {
+ // Fallback to basic Authorization if no standard auth
+ $user = BasicAuthorization::getAuthenticatedUser($request);
+ }
+ if (!isset($user) or !$user->hasPermission($this->getRequiredLevel())) {
+ abort(Response::HTTP_FORBIDDEN);
+ }
+ return $next($request);
+ }
+}
diff --git a/app/Livewire/Auth/Register.php b/app/Livewire/Auth/Register.php
index 4b753d3..88583f9 100644
--- a/app/Livewire/Auth/Register.php
+++ b/app/Livewire/Auth/Register.php
@@ -12,6 +12,7 @@
use Livewire\Attributes\Locked;
use Livewire\Attributes\Title;
use Livewire\Component;
+use const App\Models\USER_LEVEL;
#[Title('Register')]
#[Layout('components.layouts.auth')]
@@ -60,8 +61,8 @@ public function register(): void
// Since the user needs a token to register, we can consider this an email verification
$user->markEmailAsVerified();
- if ($registrant->isAdmin()) {
- $user->is_admin = true;
+ if ($registrant->getPermissionLevel() > USER_LEVEL) {
+ $user->permission_level = $registrant->getPermissionLevel();
$user->save();
}
RegistrationTokenRepository::get()->deleteExisting($this->email);
diff --git a/app/Livewire/Maven/DirectoryIndex.php b/app/Livewire/Maven/DirectoryIndex.php
index 00efd9b..90c7a57 100644
--- a/app/Livewire/Maven/DirectoryIndex.php
+++ b/app/Livewire/Maven/DirectoryIndex.php
@@ -17,10 +17,13 @@ public function mount(?string $path = '')
{
$this->isRoot = $path == '';
$this->path = $path;
- if (!Storage::directoryExists("maven$path")) {
- return redirect(route('maven.download', $path));
+ if (!Storage::disk('maven')->exists($path)) {
+ abort(404);
}
- $fullPath = Storage::path("maven/$path");
+ if (!Storage::disk('maven')->directoryExists($path)) {
+ return redirect(route('maven.download', substr($path, 1)));
+ }
+ $fullPath = Storage::disk('maven')->path($path);
foreach (new DirectoryIterator($fullPath) as $file) {
if ($file->isDot()) continue;
$path = $file->getRealPath();
diff --git a/app/Livewire/Posts/EditPost.php b/app/Livewire/Posts/EditPost.php
index 56b1a81..01ea290 100644
--- a/app/Livewire/Posts/EditPost.php
+++ b/app/Livewire/Posts/EditPost.php
@@ -4,7 +4,6 @@
use App\Models\Post;
use Illuminate\Validation\Rule;
-use Livewire\Attributes\Title;
use Livewire\Component;
use Throwable;
diff --git a/app/Livewire/Projects/EditProject.php b/app/Livewire/Projects/EditProject.php
index 46f6422..9e444ca 100644
--- a/app/Livewire/Projects/EditProject.php
+++ b/app/Livewire/Projects/EditProject.php
@@ -3,7 +3,6 @@
namespace App\Livewire\Projects;
use App\Models\Project;
-use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;
use Livewire\Component;
diff --git a/app/Livewire/Users/UserCell.php b/app/Livewire/Users/UserCell.php
index 9b4731e..07651b2 100644
--- a/app/Livewire/Users/UserCell.php
+++ b/app/Livewire/Users/UserCell.php
@@ -7,19 +7,20 @@
class UserCell extends Component
{
+
public User $user;
- public bool $isAdmin;
+ public int $permissionLevel;
public function mount(User $user)
{
$this->user = $user;
- $this->isAdmin = $user->is_admin;
+ $this->permissionLevel = $user->permission_level;
}
- public function updateIsAdmin(): void
+ public function updatePermissionLevel(): void
{
$this->authorize('update', $this->user);
- $this->user->is_admin = $this->isAdmin;
+ $this->user->permission_level = $this->permissionLevel;
$this->user->save();
}
diff --git a/app/Livewire/Users/UserIndex.php b/app/Livewire/Users/UserIndex.php
index 0fb57eb..9ad9c4e 100644
--- a/app/Livewire/Users/UserIndex.php
+++ b/app/Livewire/Users/UserIndex.php
@@ -16,7 +16,7 @@ class UserIndex extends Component
public $users;
public string $newUserEmail = '';
- public bool $newUserIsAdmin = false;
+ public int $newUserPermissionLevel = 0;
public function mount(): void
{
@@ -29,7 +29,7 @@ public function add(): void
'newUserEmail' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class.',email']
]);
- $token = RegistrationTokenRepository::get()->create(new Registrant($this->newUserEmail, $this->newUserIsAdmin));
+ $token = RegistrationTokenRepository::get()->create(new Registrant($this->newUserEmail, $this->newUserPermissionLevel));
Mail::to($this->newUserEmail)->send(new Registration($this->newUserEmail, $token));
@@ -38,7 +38,7 @@ public function add(): void
// Close modal and reset fields
$this->modal('addUser')->close();
$this->newUserEmail = '';
- $this->newUserIsAdmin = false;
+ $this->newUserPermissionLevel = 0;
}
}
diff --git a/app/Models/User.php b/app/Models/User.php
index 3cb5ccb..9f4269f 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -8,6 +8,12 @@
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Str;
+const ADMIN_LEVEL = 2;
+const MAVEN_EDITOR_LEVEL = 1;
+const USER_LEVEL = 0;
+
+const PERMISSION_LEVEL_DISPLAY_NAMES = ['User', 'Maven Editor', 'Admin'];
+
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
@@ -58,4 +64,18 @@ public function initials(): string
->map(fn ($word) => Str::substr($word, 0, 1))
->implode('');
}
+
+ public function hasPermission(int $level) {
+ return $this->permission_level >= $level;
+ }
+
+ public function isMavenEditor(): bool
+ {
+ return $this->permission_level >= MAVEN_EDITOR_LEVEL;
+ }
+
+ public function isAdmin(): bool
+ {
+ return $this->permission_level == ADMIN_LEVEL;
+ }
}
diff --git a/app/Policies/PostPolicy.php b/app/Policies/PostPolicy.php
index 4e0025a..b5c9bcd 100644
--- a/app/Policies/PostPolicy.php
+++ b/app/Policies/PostPolicy.php
@@ -4,7 +4,6 @@
use App\Models\Post;
use App\Models\User;
-use Illuminate\Auth\Access\Response;
/**
* Post policy is quite simple at the moment: any users can view any posts, and admins can use any CRUD operation on
@@ -33,7 +32,7 @@ public function view(User $user, Post $post): bool
*/
public function create(User $user): bool
{
- return isset($user) && $user->is_admin;
+ return isset($user) && $user->isAdmin();
}
/**
@@ -41,7 +40,7 @@ public function create(User $user): bool
*/
public function update(User $user, Post $post): bool
{
- return isset($user) && $user->is_admin;
+ return isset($user) && $user->isAdmin();
}
/**
@@ -49,6 +48,6 @@ public function update(User $user, Post $post): bool
*/
public function delete(User $user, Post $post): bool
{
- return isset($user) && $user->is_admin;
+ return isset($user) && $user->isAdmin();
}
}
diff --git a/app/Policies/ProjectPolicy.php b/app/Policies/ProjectPolicy.php
index 8e4bc0d..e599e0b 100644
--- a/app/Policies/ProjectPolicy.php
+++ b/app/Policies/ProjectPolicy.php
@@ -32,7 +32,7 @@ public function view(User $user, Project $project): bool
*/
public function create(User $user): bool
{
- return isset($user) && $user->is_admin;
+ return isset($user) && $user->isAdmin();
}
/**
@@ -40,7 +40,7 @@ public function create(User $user): bool
*/
public function update(User $user, Project $project): bool
{
- return isset($user) && $user->is_admin;
+ return isset($user) && $user->isAdmin();
}
/**
@@ -48,6 +48,6 @@ public function update(User $user, Project $project): bool
*/
public function delete(User $user, Project $project): bool
{
- return isset($user) && $user->is_admin;
+ return isset($user) && $user->isAdmin();
}
}
diff --git a/app/Policies/UserPolicy.php b/app/Policies/UserPolicy.php
index 20641d9..fce092e 100644
--- a/app/Policies/UserPolicy.php
+++ b/app/Policies/UserPolicy.php
@@ -12,7 +12,7 @@ class UserPolicy
*/
public function viewAny(User $user): bool
{
- return isset($user) && $user->is_admin;
+ return isset($user) && $user->isAdmin();
}
/**
@@ -20,7 +20,7 @@ public function viewAny(User $user): bool
*/
public function view(User $user, User $model): bool
{
- return isset($user) && $user->is_admin;
+ return isset($user) && $user->isAdmin();
}
/**
@@ -28,7 +28,7 @@ public function view(User $user, User $model): bool
*/
public function create(User $user): bool
{
- return isset($user) && $user->is_admin;
+ return isset($user) && $user->isAdmin();
}
/**
@@ -36,7 +36,7 @@ public function create(User $user): bool
*/
public function update(User $user, User $model): bool
{
- return isset($user) && $user->is_admin;
+ return isset($user) && $user->isAdmin();
}
/**
@@ -44,7 +44,7 @@ public function update(User $user, User $model): bool
*/
public function delete(User $user, User $model): bool
{
- return isset($user) && $user->is_admin;
+ return isset($user) && $user->isAdmin();
}
}
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 937b7fe..531f377 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -21,7 +21,7 @@ public function register(): void
public function boot(): void
{
Blade::if('isAdmin', function () {
- return auth()->check() && auth()->user()->is_admin;
+ return auth()->check() && auth()->user()->isAdmin();
});
}
}
diff --git a/app/helpers.php b/app/helpers.php
index e17e126..05b860b 100644
--- a/app/helpers.php
+++ b/app/helpers.php
@@ -1,11 +1,13 @@
first();
- return $user && Hash::check($password, $user->password);
+function upload_to_maven($file, $path) {
+ $upload = fopen($path, 'w');
+ $contents = $file instanceof UploadedFile ? $file->getContent() : stream_get_contents($file);
+ if (!$upload || !fwrite($upload, $contents)) {
+ return false;
+ }
+ fclose($upload);
+ return true;
}
diff --git a/bootstrap/app.php b/bootstrap/app.php
index cbc3269..cf53dcd 100644
--- a/bootstrap/app.php
+++ b/bootstrap/app.php
@@ -1,5 +1,8 @@
withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'maven/*'
+ ])->alias([
+ 'basic_auth' => BasicAuthorization::class,
+ 'maven_editor_auth' => MavenEditorAuthorization::class,
+ 'admin_auth' => AdminAuthorization::class
]);
})
->withExceptions(function (Exceptions $exceptions) {
diff --git a/config/filesystems.php b/config/filesystems.php
index b2326a8..2001572 100644
--- a/config/filesystems.php
+++ b/config/filesystems.php
@@ -72,6 +72,12 @@
'visibility' => 'public'
],
+ 'maven' => [
+ 'driver' => 'local',
+ 'root' => storage_path('maven'),
+ 'visibility' => 'public',
+ ],
+
],
/*
diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php
index 8dd8a17..6772aaf 100644
--- a/database/factories/UserFactory.php
+++ b/database/factories/UserFactory.php
@@ -5,6 +5,9 @@
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
+use const App\Models\ADMIN_LEVEL;
+use const App\Models\MAVEN_EDITOR_LEVEL;
+use const App\Models\USER_LEVEL;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
@@ -29,7 +32,7 @@ public function definition(): array
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
- 'is_admin' => false
+ 'permission_level' => 0
];
}
@@ -43,10 +46,24 @@ public function unverified(): static
]);
}
+ public function mavenUser(): static
+ {
+ return $this->state(fn (array $attributes) => [
+ 'permission_level' => USER_LEVEL,
+ ]);
+ }
+
+ public function mavenEditor()
+ {
+ return $this->static(fn (array $attributes) => [
+ 'permission_level' => MAVEN_EDITOR_LEVEL,
+ ]);
+ }
+
public function admin(): static
{
return $this->state(fn (array $attributes) => [
- 'is_admin' => true,
+ 'permission_level' => ADMIN_LEVEL,
]);
}
}
diff --git a/database/migrations/2026_02_12_195742_replace_is_admin_with_permission_level_in_users_table.php b/database/migrations/2026_02_12_195742_replace_is_admin_with_permission_level_in_users_table.php
new file mode 100644
index 0000000..ec65c6d
--- /dev/null
+++ b/database/migrations/2026_02_12_195742_replace_is_admin_with_permission_level_in_users_table.php
@@ -0,0 +1,36 @@
+renameColumn('is_admin', 'permission_level');
+ $table->integer('permission_level')->unsigned()->default(0)->change();
+ });
+ DB::table('users')
+ ->where('permission_level', '=', 1)
+ ->update(['permission_level' => 2]);
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('users', function (Blueprint $table) {
+ $table->renameColumn('permission_level', 'is_admin');
+ $table->boolean('is_admin')->default(false)->change();
+ });
+ DB::table('users')
+ ->where('is_admin', '>', 1)
+ ->update(['is_admin' => 1]);
+ }
+};
diff --git a/database/migrations/2026_02_14_170418_replace_is_admin_with_permission_level_in_registration_tokens_table.php b/database/migrations/2026_02_14_170418_replace_is_admin_with_permission_level_in_registration_tokens_table.php
new file mode 100644
index 0000000..7742e9d
--- /dev/null
+++ b/database/migrations/2026_02_14_170418_replace_is_admin_with_permission_level_in_registration_tokens_table.php
@@ -0,0 +1,36 @@
+renameColumn('is_admin', 'permission_level');
+ $table->integer('permission_level')->unsigned()->default(0)->change();
+ });
+ DB::table('registration_tokens')
+ ->where('permission_level', '=', 1)
+ ->update(['permission_level' => 2]);
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('registration_tokens', function (Blueprint $table) {
+ $table->renameColumn('permission_level', 'is_admin');
+ $table->boolean('is_admin')->default(false)->change();
+ });
+ DB::table('registration_tokens')
+ ->where('is_admin', '>', 1)
+ ->update(['is_admin' => 1]);
+ }
+};
diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php
index bcaec90..cf82843 100644
--- a/database/seeders/DatabaseSeeder.php
+++ b/database/seeders/DatabaseSeeder.php
@@ -5,6 +5,7 @@
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
+use const App\Models\ADMIN_LEVEL;
class DatabaseSeeder extends Seeder
{
@@ -19,7 +20,7 @@ public function run(): void
'name' => 'Harley O\'Connor',
'email' => 'admin@harleyoconnor.com',
'password' => Hash::make($password),
- 'is_admin' => true,
+ 'permission_level' => ADMIN_LEVEL,
]);
}
}
diff --git a/resources/views/livewire/users/user-cell.blade.php b/resources/views/livewire/users/user-cell.blade.php
index 804d7fd..9a64ac3 100644
--- a/resources/views/livewire/users/user-cell.blade.php
+++ b/resources/views/livewire/users/user-cell.blade.php
@@ -1,3 +1,4 @@
+@php use const App\Models\PERMISSION_LEVEL_DISPLAY_NAMES; @endphp