feat(auth): enforce strict branch scoping for branch-assigned HR and staff accounts

- Introduce authBranchId() helper to resolve assigned branch while allowing company admin/superadmin cross-branch oversight
- Restrict branch, department, and employee listings and mutations to assigned branch for branch-scoped accounts
- Scope attendance records, biometric logs, leave applications/balances, payslips, shifts, and announcements by assigned branch
- Add comprehensive automated feature tests in BranchScopedAccessTest to prevent branch cross-visibility regression
This commit is contained in:
2026-09-10 11:14:24 +08:00
parent 4b350c4628
commit 7f467600e9
18 changed files with 937 additions and 144 deletions

View File

@@ -25,9 +25,12 @@ class UserController extends BaseController
abort(403, 'Unauthorized Access Prevented');
}
// $userQuery = User::withPermissionCheck()->with(['roles', 'creator'])->latest();
$userQuery = User::with(['roles', 'creator'])->where(function ($q) {
if (Auth::user()->can('manage-any-users')) {
$scopedBranchId = authBranchId();
$userQuery = User::with(['roles', 'creator'])->where(function ($q) use ($scopedBranchId) {
if ($scopedBranchId) {
$q->whereIn('created_by', getCompanyAndUsersId())
->where('branch_id', $scopedBranchId);
} elseif (Auth::user()->can('manage-any-users')) {
$q->whereIn('created_by', getCompanyAndUsersId());
} elseif (Auth::user()->can('manage-own-users')) {
$q->where('created_by', Auth::id());
@@ -121,7 +124,11 @@ class UserController extends BaseController
}
}
$branches = \App\Models\Branch::whereIn('created_by', getCompanyAndUsersId())->get(['id', 'name']);
$branchesQuery = \App\Models\Branch::whereIn('created_by', getCompanyAndUsersId());
if ($scopedBranchId) {
$branchesQuery->where('id', $scopedBranchId);
}
$branches = $branchesQuery->get(['id', 'name']);
return Inertia::render('users/index', [
'users' => $users,
@@ -179,13 +186,16 @@ class UserController extends BaseController
$created_by = auth()->id();
}
$scopedBranchId = authBranchId();
$assignedBranchId = $scopedBranchId ?: $request->branch_id;
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'created_by' => creatorId(),
'lang' => $userLang,
'branch_id' => $request->branch_id,
'branch_id' => $assignedBranchId,
]);
if ($user && $request->roles) {
@@ -222,10 +232,17 @@ class UserController extends BaseController
public function update(UserRequest $request, User $user)
{
if (Auth::user()->can('edit-users')) {
$scopedBranchId = authBranchId();
if ($scopedBranchId && (int)$user->branch_id !== (int)$scopedBranchId) {
return redirect()->back()->with('error', __('Permission Denied. You cannot edit users outside your assigned branch.'));
}
if ($user) {
$user->name = $request->name;
$user->email = $request->email;
if ($request->has('branch_id')) {
if ($scopedBranchId) {
$user->branch_id = $scopedBranchId;
} elseif ($request->has('branch_id')) {
$user->branch_id = $request->branch_id;
}
@@ -261,6 +278,11 @@ class UserController extends BaseController
public function destroy(User $user)
{
if (Auth::user()->can('delete-users')) {
$scopedBranchId = authBranchId();
if ($scopedBranchId && (int)$user->branch_id !== (int)$scopedBranchId) {
return redirect()->back()->with('error', __('Permission Denied. You cannot delete users outside your assigned branch.'));
}
if ($user) {
$user->delete();
return redirect()->route('users.index')->with('success', __('User deleted with roles'));