-
Notifications
You must be signed in to change notification settings - Fork 49
Feat/user resource panel admin #487
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 4.x
Are you sure you want to change the base?
Changes from all commits
0f6baed
87bada0
91d9c7b
ec9d5b3
bc3d71e
310e496
7fb9153
b705e4c
a96d689
3bbab7a
71a4388
187e3e7
c4ba82c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| use Illuminate\Database\Migrations\Migration; | ||
| use Illuminate\Database\Schema\Blueprint; | ||
| use Illuminate\Support\Facades\Schema; | ||
|
|
||
| return new class extends Migration | ||
| { | ||
| public function up(): void | ||
| { | ||
| Schema::table('users', static function (Blueprint $table): void { | ||
| $table->timestampTz('deleted_at')->nullable(); | ||
| }); | ||
| } | ||
|
|
||
| public function down(): void | ||
| { | ||
| Schema::table('users', static function (Blueprint $table): void { | ||
| $table->dropColumn('deleted_at'); | ||
| }); | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| use Illuminate\Database\Migrations\Migration; | ||
| use Illuminate\Support\Facades\DB; | ||
|
|
||
| return new class extends Migration | ||
| { | ||
| /** | ||
| * Uma conta soft-deletada não pode travar o `username` pra sempre — sem | ||
| * isso, `MergeAccountsAction` (e qualquer novo cadastro) esbarra em | ||
| * "duplicate key" ao tentar reaproveitar o username de um usuário | ||
| * apenas soft-deletado. | ||
| */ | ||
| public function up(): void | ||
| { | ||
| DB::statement('ALTER TABLE users DROP CONSTRAINT users_username_unique'); | ||
| DB::statement('CREATE UNIQUE INDEX users_username_unique ON users (username) WHERE deleted_at IS NULL'); | ||
| } | ||
|
|
||
| public function down(): void | ||
| { | ||
| DB::statement('DROP INDEX users_username_unique'); | ||
| DB::statement('ALTER TABLE users ADD CONSTRAINT users_username_unique UNIQUE (username)'); | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace He4rt\Identity\Auth\Exceptions; | ||
|
|
||
| final class AccountSoftDeletedException extends OAuthFlowException | ||
| { | ||
| public static function make(): self | ||
| { | ||
| return new self('This account was deleted and cannot be reactivated by logging in again.'); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,8 @@ | |
| use He4rt\Identity\User\Enums\UserSituation; | ||
| use He4rt\Identity\User\Observers\UserObserver; | ||
| use He4rt\Profile\Models\Profile; | ||
| use He4rt\Profile\Models\ProfileSkill; | ||
| use He4rt\Profile\Models\WorkExperience; | ||
| use Illuminate\Database\Eloquent\Attributes\Hidden; | ||
| use Illuminate\Database\Eloquent\Attributes\ObservedBy; | ||
| use Illuminate\Database\Eloquent\Attributes\Table; | ||
|
|
@@ -26,8 +28,10 @@ | |
| use Illuminate\Database\Eloquent\Collection; | ||
| use Illuminate\Database\Eloquent\Concerns\HasUuids; | ||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | ||
| use Illuminate\Database\Eloquent\Relations\HasManyThrough; | ||
| use Illuminate\Database\Eloquent\Relations\HasOne; | ||
| use Illuminate\Database\Eloquent\Relations\MorphMany; | ||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||
| use Illuminate\Foundation\Auth\User as Authenticatable; | ||
| use Illuminate\Notifications\Notifiable; | ||
| use Spatie\MediaLibrary\HasMedia; | ||
|
|
@@ -44,9 +48,10 @@ | |
| * @property CarbonInterface|null $suspended_until | ||
| * @property CarbonInterface|null $banned_at | ||
| * @property CarbonInterface|null $first_login_at | ||
| * @property string|null $remember_token | ||
| * @property CarbonInterface|null $deleted_at | ||
| * @property CarbonInterface|null $created_at | ||
| * @property CarbonInterface|null $updated_at | ||
| * @property string|null $remember_token | ||
| * @property-read UserSituation $situation | ||
| * @property-read Collection<int, Role> $roles | ||
| */ | ||
|
|
@@ -65,12 +70,47 @@ final class User extends Authenticatable implements FilamentUser, HasMedia, HasN | |
| use HasUuids; | ||
| use InteractsWithMedia; | ||
| use Notifiable; | ||
| use SoftDeletes; | ||
|
|
||
| public function isSuperAdmin(): bool | ||
| { | ||
| return $this->hasRole(UserRole::SuperAdmin); | ||
| } | ||
|
|
||
| public function isStaff(): bool | ||
| { | ||
| return $this->hasRole(UserRole::Staff); | ||
| } | ||
|
|
||
| public function isCompliance(): bool | ||
| { | ||
| return $this->hasRole(UserRole::Compliance); | ||
| } | ||
|
|
||
| /** | ||
| * Quem gerencia usuários: edita identidade/perfil/endereço e soft-deleta. | ||
| */ | ||
| public function canManageUsers(): bool | ||
| { | ||
| return $this->isSuperAdmin() || $this->isStaff() || $this->isCompliance(); | ||
|
Comment on lines
+93
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🛡️ Analyzed with Security Review | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- candidate files ---'
fd -i 'User.php|UserResource.php|UserForm.php|EditUser.php|CreateUser.php|UserPolicy.php' app-modules
printf '%s\n' '--- User model outline ---'
ast-grep outline app-modules/identity/src/User/Models/User.php --view expanded
printf '%s\n' '--- User model authorization slice ---'
sed -n '1,180p' app-modules/identity/src/User/Models/User.php
printf '%s\n' '--- User resource ---'
sed -n '1,150p' app-modules/panel-admin/src/Filament/Resources/Users/UserResource.php
printf '%s\n' '--- user resource page references ---'
rg -n -S 'UserResource|EditUser|CreateUser|roles|canManageUsers|assignRole|syncRoles|mutateFormData|beforeSave|afterSave' app-modules/panel-admin/src app-modules/identity/srcRepository: he4rt/heartdevs.com Length of output: 23617 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- edit page ---'
cat -n app-modules/panel-admin/src/Filament/Resources/Users/Pages/EditUser.php
printf '%s\n' '--- remaining user form ---'
sed -n '150,230p' app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserForm.php
printf '%s\n' '--- relevant package declarations ---'
rg -n -S '"filament/|filamentphp|spatie/laravel-permission' composer.json app-modules --glob 'composer.json'Repository: he4rt/heartdevs.com Length of output: 2657 Authorization Bypass Reachability: External Restrict privileged role assignment. 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| /** | ||
| * Hard delete é exclusivo de Compliance (e super admin, que sobrepõe tudo). | ||
| */ | ||
| public function canHardDeleteUsers(): bool | ||
| { | ||
| return $this->isSuperAdmin() || $this->isCompliance(); | ||
| } | ||
|
|
||
| /** | ||
| * Recruiter/SquadCaptain veem a ficha do usuário, mas não a seção de Moderação. | ||
| */ | ||
| public function canViewModeration(): bool | ||
| { | ||
| return $this->isSuperAdmin() || $this->isStaff() || $this->isCompliance(); | ||
| } | ||
|
|
||
| /** | ||
| * @return MorphMany<ExternalIdentity, $this> | ||
| */ | ||
|
|
@@ -95,6 +135,36 @@ public function profile(): HasOne | |
| return $this->hasOne(Profile::class); | ||
| } | ||
|
|
||
| /** | ||
| * @return HasManyThrough<ProfileSkill, Profile, $this> | ||
| */ | ||
| public function profileSkills(): HasManyThrough | ||
| { | ||
| return $this->hasManyThrough( | ||
| ProfileSkill::class, | ||
| Profile::class, | ||
| 'user_id', | ||
| 'profile_id', | ||
| 'id', | ||
| 'id', | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * @return HasManyThrough<WorkExperience, Profile, $this> | ||
| */ | ||
| public function workExperiences(): HasManyThrough | ||
| { | ||
| return $this->hasManyThrough( | ||
| WorkExperience::class, | ||
| Profile::class, | ||
| 'user_id', | ||
| 'profile_id', | ||
| 'id', | ||
| 'id', | ||
| ); | ||
| } | ||
|
|
||
| public function getFilamentName(): string | ||
| { | ||
| return $this->username; | ||
|
|
@@ -108,7 +178,13 @@ public function registerMediaCollections(): void | |
| public function canAccessPanel(Panel $panel): bool | ||
| { | ||
| return match ($panel->getId()) { | ||
| 'admin' => app()->isProduction() ? $this->isSuperAdmin() : true, | ||
| 'admin' => app()->isProduction() ? $this->hasAnyRole([ | ||
| UserRole::SuperAdmin, | ||
| UserRole::Staff, | ||
| UserRole::Compliance, | ||
| UserRole::Recruiter, | ||
| UserRole::SquadCaptain, | ||
| ]) : true, | ||
| default => true | ||
| }; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
The partial index permits reusing a soft-deleted username, but
down()restores an unconditional unique constraint without reconciling those now-valid duplicates. A rollback after reuse will fail while adding the constraint; make the rollback handle duplicates explicitly or document/implement an irreversible migration strategy.🧰 Tools
🪛 ast-grep (0.45.3)
[error] 23-23: Prevent raw SQL injections
Context: DB::statement('DROP INDEX users_username_unique')
Note: [CWE-89] Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection').
(laravel-raw-sql-injection)
[error] 24-24: Prevent raw SQL injections
Context: DB::statement('ALTER TABLE users ADD CONSTRAINT users_username_unique UNIQUE (username)')
Note: [CWE-89] Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection').
(laravel-raw-sql-injection)
🤖 Prompt for AI Agents