Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
version: 2

updates:
# PHP / Composer dependencies
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
groups:
composer-minor-patch:
update-types:
- "minor"
- "patch"

# JavaScript / npm dependencies
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
groups:
npm-minor-patch:
update-types:
- "minor"
- "patch"

# GitHub Actions workflow dependencies
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
23 changes: 23 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## Summary

<!-- Briefly describe what this PR changes and why. -->

## Type of change

- [ ] Bug fix
- [ ] New feature
- [ ] Refactor / code quality
- [ ] Tooling / CI
- [ ] Documentation

## Checklist

- [ ] `vendor/bin/pint --test` passes (code style)
- [ ] `vendor/bin/phpstan analyse` passes (static analysis)
- [ ] `php artisan test` passes (test suite)
- [ ] `npm run build` succeeds (frontend build), if applicable
- [ ] Documentation / CHANGELOG updated, if applicable

## Notes

<!-- Anything reviewers should pay special attention to. -->
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand All @@ -37,6 +37,12 @@ jobs:
- name: Install PHP dependencies
run: composer install --no-interaction --prefer-dist --no-progress

- name: Check code style (Pint)
run: vendor/bin/pint --test

- name: Static analysis (PHPStan)
run: vendor/bin/phpstan analyse --no-progress

- name: Prepare environment
run: |
cp .env.example .env
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ yarn-error.log
.github/skills/
!.github/workflows/
!.github/workflows/*.yml
!.github/dependabot.yml
!.github/pull_request_template.md
openspec
# OS Files
.DS_Store
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@

All notable changes to this project will be documented in this file.

## [4.2.0] - 2026-07-21

### Security
- Refreshed backend dependencies within their existing constraints to clear all reported advisories (`laravel/framework`, `symfony/*`, `guzzlehttp/*`). `composer audit` now reports no vulnerabilities.
- Refreshed frontend dependencies (`npm audit fix`) to clear all reported advisories, including the high-severity `axios` issues. `npm audit` now reports no vulnerabilities.
- Restricted the file manager `show` endpoint to an explicit safe field subset so internal columns (storage path, owner id) are never exposed to the client.

### Added
- Static analysis via Larastan / PHPStan (level 5), wired into `composer analyse` and the CI pipeline.
- `pint.json` (Laravel preset) plus `composer lint` / `composer lint:test`, with Pint style checking enforced in CI.
- Dependabot configuration for weekly Composer, npm, and GitHub Actions updates.
- Pull request template.
- Feature tests for the granular permission gates and the hardened file manager `show` response.

### Changed
- Admin routes are now gated with granular Spatie permissions (`role_or_permission:superadmin|<permission>`) instead of only the coarse `superadmin` role, so non-superadmin roles can be granted specific capabilities. Existing superadmin behaviour is unchanged.
- Adopted route-model binding for user, activity-log, and file routes; the `{file}` binding is scoped to the authenticated user so foreign files resolve to a 404.
- Form Requests for settings, notifications, and role management now enforce authorization instead of returning `true`.

### Fixed
- Resolved all PHPStan level 5 findings (reflection type guard, model factory return types, and `list<string>` property annotations).

## [4.1.0] - 2026-04-24

### Updated
Expand Down
10 changes: 10 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,12 @@ php artisan route:list --except-vendor
# Check code style (PSR-12)
./vendor/bin/pint --test

# Auto-fix code style
composer lint

# Run static analysis (Larastan / PHPStan, level 5)
composer analyse

# Remove development artifacts from vendored public assets
php artisan assets:prune-vendored

Expand All @@ -322,11 +328,15 @@ GitHub Actions workflow: `.github/workflows/ci.yml`
|---|---|
| PHP 8.4 + SQLite | Environment setup |
| `composer install` | Backend dependencies |
| `vendor/bin/pint --test` | Code style check |
| `vendor/bin/phpstan analyse` | Static analysis (Larastan, level 5) |
| `php artisan migrate` | Database schema |
| `php artisan route:list` | Route integrity check |
| `php artisan test` | Test suite |
| `npm ci` + `npm run build` | Frontend build |

Dependency updates are automated with [Dependabot](.github/dependabot.yml) (Composer, npm, and GitHub Actions, weekly).

---

## Security
Expand Down
2 changes: 1 addition & 1 deletion app/Console/Commands/Traits/GeneratorHelpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ protected function detectRelationships(string $modelClass): array
foreach ($methods as $method) {
$returnType = $method->getReturnType();

if (! $returnType) {
if (! $returnType instanceof \ReflectionNamedType) {
continue;
}

Expand Down
9 changes: 4 additions & 5 deletions app/Http/Controllers/ActivityLogController.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,19 @@ public function index(Request $request): View
/**
* Display the specified activity log.
*/
public function show(int $id): View
public function show(ActivityLog $activityLog): View
{
$log = ActivityLog::with('user')->findOrFail($id);
$log = $activityLog->load('user');

return view('activity-logs.show', compact('log'));
}

/**
* Remove the specified activity log from storage.
*/
public function destroy(int $id): RedirectResponse
public function destroy(ActivityLog $activityLog): RedirectResponse
{
$log = ActivityLog::findOrFail($id);
$log->delete();
$activityLog->delete();

return redirect()->route('activity-logs.index')
->with('success', 'Activity log deleted successfully.');
Expand Down
32 changes: 19 additions & 13 deletions app/Http/Controllers/FileManagerController.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,8 @@ public function upload(UploadFilesRequest $request): RedirectResponse
/**
* Download a file.
*/
public function download(int $id): StreamedResponse
public function download(File $file): StreamedResponse
{
$file = File::where('user_id', auth()->id())->findOrFail($id);

ActivityLog::log(
'File downloaded: '.$file->original_name,
'File Manager',
Expand All @@ -115,10 +113,8 @@ public function download(int $id): StreamedResponse
/**
* Update file details.
*/
public function update(UpdateFileRequest $request, int $id): RedirectResponse
public function update(UpdateFileRequest $request, File $file): RedirectResponse
{
$file = File::where('user_id', auth()->id())->findOrFail($id);

$validated = $request->only(['original_name', 'description', 'is_public']);
$validated['folder'] = FolderPath::normalize((string) $request->input('folder', '/'));

Expand All @@ -138,10 +134,8 @@ public function update(UpdateFileRequest $request, int $id): RedirectResponse
/**
* Delete a file.
*/
public function destroy(int $id): RedirectResponse
public function destroy(File $file): RedirectResponse
{
$file = File::where('user_id', auth()->id())->findOrFail($id);

// Delete physical file
if (Storage::disk('public')->exists($file->path)) {
Storage::disk('public')->delete($file->path);
Expand All @@ -164,12 +158,24 @@ public function destroy(int $id): RedirectResponse

/**
* Get file details (AJAX).
*
* Returns a safe, explicit subset of attributes so internal columns such
* as the storage path and owner id are never exposed to the client.
*/
public function show(int $id): JsonResponse
public function show(File $file): JsonResponse
{
$file = File::where('user_id', auth()->id())->findOrFail($id);

return response()->json($file);
return response()->json([
'id' => $file->id,
'original_name' => $file->original_name,
'description' => $file->description,
'is_public' => $file->is_public,
'folder' => $file->folder,
'mime_type' => $file->mime_type,
'extension' => $file->extension,
'formatted_size' => $file->formatted_size,
'url' => $file->url,
'created_at' => $file->created_at,
]);
}

/**
Expand Down
11 changes: 4 additions & 7 deletions app/Http/Controllers/HakaksesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,18 @@ public function index(Request $request): View
/**
* Show the form for editing the specified user's role.
*/
public function edit(int $id): View
public function edit(User $user): View
{
$hakakses = User::findOrFail($id);
$hakakses = $user;

return view('layouts.hakakses.edit', compact('hakakses'));
}

/**
* Update the specified user's role.
*/
public function update(UpdateUserRoleRequest $request, int $id): RedirectResponse
public function update(UpdateUserRoleRequest $request, User $user): RedirectResponse
{
$user = User::findOrFail($id);
$user->syncRoles([$request->role]);

ActivityLog::log(
Expand All @@ -68,10 +67,8 @@ public function update(UpdateUserRoleRequest $request, int $id): RedirectRespons
/**
* Remove the specified user from storage.
*/
public function destroy(int $id): RedirectResponse
public function destroy(User $user): RedirectResponse
{
$user = User::findOrFail($id);

// Prevent deleting yourself
if ($user->id === auth()->id()) {
return redirect()->route('hakakses.index')
Expand Down
5 changes: 4 additions & 1 deletion app/Http/Requests/Notifications/SendNotificationRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ class SendNotificationRequest extends FormRequest
{
public function authorize(): bool
{
return true;
$user = $this->user();

return $user !== null
&& ($user->hasRole('superadmin') || $user->can('send-notifications'));
}

/**
Expand Down
5 changes: 4 additions & 1 deletion app/Http/Requests/Roles/UpdateUserRoleRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ class UpdateUserRoleRequest extends FormRequest
{
public function authorize(): bool
{
return true;
$user = $this->user();

return $user !== null
&& ($user->hasRole('superadmin') || $user->can('manage-users'));
}

/**
Expand Down
5 changes: 4 additions & 1 deletion app/Http/Requests/Settings/StoreSettingRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ class StoreSettingRequest extends FormRequest
{
public function authorize(): bool
{
return true;
$user = $this->user();

return $user !== null
&& ($user->hasRole('superadmin') || $user->can('manage-settings'));
}

/**
Expand Down
5 changes: 4 additions & 1 deletion app/Http/Requests/Settings/UpdateSettingsRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ class UpdateSettingsRequest extends FormRequest
{
public function authorize(): bool
{
return true;
$user = $this->user();

return $user !== null
&& ($user->hasRole('superadmin') || $user->can('manage-settings'));
}

/**
Expand Down
10 changes: 5 additions & 5 deletions app/Models/ActivityLog.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public function model(): MorphTo
/**
* Log a general activity.
*/
public static function log(string $description, ?string $subject = null, string $event = 'custom', $model = null, array $properties = []): static
public static function log(string $description, ?string $subject = null, string $event = 'custom', $model = null, array $properties = []): self
{
return static::create([
'user_id' => auth()->id(),
Expand All @@ -90,7 +90,7 @@ public static function log(string $description, ?string $subject = null, string
/**
* Log user login.
*/
public static function logLogin($user): static
public static function logLogin($user): self
{
return static::create([
'user_id' => $user->id,
Expand All @@ -105,7 +105,7 @@ public static function logLogin($user): static
/**
* Log user logout.
*/
public static function logLogout($user): static
public static function logLogout($user): self
{
return static::create([
'user_id' => $user->id,
Expand All @@ -120,7 +120,7 @@ public static function logLogout($user): static
/**
* Log profile update.
*/
public static function logProfileUpdate($user): static
public static function logProfileUpdate($user): self
{
return static::create([
'user_id' => $user->id,
Expand All @@ -137,7 +137,7 @@ public static function logProfileUpdate($user): static
/**
* Log password change.
*/
public static function logPasswordChange($user): static
public static function logPasswordChange($user): self
{
return static::create([
'user_id' => $user->id,
Expand Down
2 changes: 1 addition & 1 deletion app/Models/Setting.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public static function get(string $key, mixed $default = null): mixed
/**
* Set a setting value.
*/
public static function set(string $key, mixed $value, string $type = 'text'): static
public static function set(string $key, mixed $value, string $type = 'text'): self
{
$setting = static::updateOrCreate(
['key' => $key],
Expand Down
Loading
Loading