feat: [users] 슈퍼관리자 보호 기능 구현
- 일반관리자가 슈퍼관리자 수정/삭제 불가 - API Controller: update/destroy에서 403 반환 - Web Controller: edit에서 403 abort - FormRequest: is_super_admin 필드 강제/유지 처리 - View: 테이블, 모달, 생성/수정 폼에서 버튼/체크박스 숨김 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -93,6 +93,15 @@ public function store(StoreUserRequest $request): JsonResponse
|
||||
public function update(UpdateUserRequest $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자를 수정하려는 경우 차단
|
||||
$targetUser = $this->userService->getUserById($id);
|
||||
if ($targetUser?->is_super_admin && ! auth()->user()?->is_super_admin) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => '슈퍼관리자는 수정할 수 없습니다.',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$result = $this->userService->updateUser($id, $request->validated());
|
||||
|
||||
if (! $result) {
|
||||
@@ -121,6 +130,15 @@ public function update(UpdateUserRequest $request, int $id): JsonResponse
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자를 삭제하려는 경우 차단
|
||||
$targetUser = $this->userService->getUserById($id);
|
||||
if ($targetUser?->is_super_admin && ! auth()->user()?->is_super_admin) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => '슈퍼관리자는 삭제할 수 없습니다.',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$result = $this->userService->deleteUser($id);
|
||||
|
||||
if (! $result) {
|
||||
|
||||
@@ -47,6 +47,11 @@ public function edit(int $id): View
|
||||
abort(404, '사용자를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자를 수정하려는 경우 차단
|
||||
if ($user->is_super_admin && ! auth()->user()?->is_super_admin) {
|
||||
abort(403, '슈퍼관리자는 수정할 수 없습니다.');
|
||||
}
|
||||
|
||||
$tenantId = session('selected_tenant_id');
|
||||
|
||||
// 역할/부서 목록 (테넌트별)
|
||||
|
||||
@@ -14,6 +14,20 @@ public function authorize(): bool
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
* 일반관리자가 슈퍼관리자를 생성하려는 경우 is_super_admin 필드 제거
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
// 슈퍼관리자가 아닌 경우 is_super_admin 필드를 false로 강제 설정
|
||||
if (! auth()->user()?->is_super_admin) {
|
||||
$this->merge([
|
||||
'is_super_admin' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
|
||||
@@ -15,6 +15,27 @@ public function authorize(): bool
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
* 일반관리자가 슈퍼관리자 관련 필드를 변경하려는 경우 처리
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$userId = $this->route('id');
|
||||
$targetUser = \App\Models\User::find($userId);
|
||||
$currentUser = auth()->user();
|
||||
|
||||
// 슈퍼관리자가 아닌 경우
|
||||
if (! $currentUser?->is_super_admin) {
|
||||
// is_super_admin 필드가 있으면 제거 (기존 값 유지)
|
||||
if ($this->has('is_super_admin')) {
|
||||
$this->merge([
|
||||
'is_super_admin' => $targetUser?->is_super_admin ?? false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
|
||||
@@ -139,11 +139,13 @@ class="h-4 w-4 text-green-600 rounded focus:ring-2 focus:ring-green-500">
|
||||
class="h-4 w-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500">
|
||||
<span class="ml-2 text-sm text-gray-700">활성 상태</span>
|
||||
</label>
|
||||
@if(auth()->user()?->is_super_admin)
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="is_super_admin" value="1"
|
||||
class="h-4 w-4 text-red-600 rounded focus:ring-2 focus:ring-red-500">
|
||||
<span class="ml-2 text-sm text-gray-700">슈퍼 관리자</span>
|
||||
</label>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -144,12 +144,14 @@ class="h-4 w-4 text-green-600 rounded focus:ring-2 focus:ring-green-500">
|
||||
class="h-4 w-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500">
|
||||
<span class="ml-2 text-sm text-gray-700">활성 상태</span>
|
||||
</label>
|
||||
@if(auth()->user()?->is_super_admin)
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="is_super_admin" value="1"
|
||||
{{ old('is_super_admin', $user->is_super_admin) ? 'checked' : '' }}
|
||||
class="h-4 w-4 text-red-600 rounded focus:ring-2 focus:ring-red-500">
|
||||
<span class="ml-2 text-sm text-gray-700">슈퍼 관리자</span>
|
||||
</label>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -179,23 +179,31 @@ class="text-xs text-blue-600 hover:text-blue-800 hover:underline">
|
||||
</div>
|
||||
|
||||
{{-- 하단 버튼 --}}
|
||||
@php
|
||||
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자를 수정/삭제할 수 없음
|
||||
$canModify = ! $user->is_super_admin || auth()->user()?->is_super_admin;
|
||||
@endphp
|
||||
<div class="flex justify-end gap-2 mt-6 pt-4 border-t border-gray-200">
|
||||
<button type="button"
|
||||
onclick="UserModal.close()"
|
||||
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50">
|
||||
닫기
|
||||
</button>
|
||||
@if(!$user->deleted_at)
|
||||
@if($canModify)
|
||||
@if(!$user->deleted_at)
|
||||
<button type="button"
|
||||
onclick="UserModal.deleteUser()"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-700">
|
||||
삭제
|
||||
</button>
|
||||
@endif
|
||||
<button type="button"
|
||||
onclick="UserModal.deleteUser()"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-700">
|
||||
삭제
|
||||
onclick="UserModal.goToEdit()"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700">
|
||||
수정
|
||||
</button>
|
||||
@else
|
||||
<span class="px-4 py-2 text-sm text-gray-400">슈퍼관리자는 수정할 수 없습니다</span>
|
||||
@endif
|
||||
<button type="button"
|
||||
onclick="UserModal.goToEdit()"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700">
|
||||
수정
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -78,6 +78,10 @@
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium" onclick="event.stopPropagation()">
|
||||
@php
|
||||
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자를 수정/삭제할 수 없음
|
||||
$canModify = ! $user->is_super_admin || auth()->user()?->is_super_admin;
|
||||
@endphp
|
||||
@if($user->deleted_at)
|
||||
<!-- 삭제된 항목 - 슈퍼관리자만 복구/영구삭제 가능 -->
|
||||
@if(auth()->user()?->is_super_admin)
|
||||
@@ -92,8 +96,8 @@ class="text-red-600 hover:text-red-900">
|
||||
@else
|
||||
<span class="text-gray-400 text-xs">삭제됨</span>
|
||||
@endif
|
||||
@else
|
||||
<!-- 활성 항목 -->
|
||||
@elseif($canModify)
|
||||
<!-- 활성 항목 (수정 가능한 경우만) -->
|
||||
<a href="{{ route('users.edit', $user->id) }}"
|
||||
onclick="event.stopPropagation()"
|
||||
class="text-blue-600 hover:text-blue-900 mr-3">
|
||||
@@ -102,6 +106,9 @@ class="text-blue-600 hover:text-blue-900 mr-3">
|
||||
<button onclick="confirmDelete({{ $user->id }}, '{{ $user->name }}')" class="text-red-600 hover:text-red-900">
|
||||
삭제
|
||||
</button>
|
||||
@else
|
||||
<!-- 슈퍼관리자 - 일반관리자는 수정/삭제 불가 -->
|
||||
<span class="text-gray-400 text-xs">수정 불가</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user