Fix critical bug where non-Admin users could affect other users' data when using "取消" (Uncheck All) and "确认删除" (Confirm Deletion) buttons. Problem: - User users see only their filtered materials in table (filteredResults) - "取消" button was unchecking ALL materials in validationResults - "确认删除" was processing ALL materials, not just visible ones - This caused User A to delete/modify User B's invisible data Solution: 1. Modified "取消" button to only uncheck visible filteredResults - Now removes selectedItems only for visible material codes - Preserves selections for other users' invisible data 2. Modified handleConfirmDeletion to process only visible items for non-Admin users - Admin: processes all validationResults (unchanged behavior) - User: processes only filteredResults (scoped to their data) Security Impact: - Prevents cross-user data interference - Ensures User scope isolation - Maintains Admin full access to all data Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
7.6 KiB
7.6 KiB
CleanerPage User Scope Fix
Issue: User users were affecting other users' data when using "取消" and "确认删除" buttons
Date: 2026-03-03
Branch: fix/cleaner-user-scope
Problem Analysis
Bug Description
For User type (non-Admin) users:
- The table shows only materials assigned to the current user (filtered by
filteredResults) - Clicking "取消" (Uncheck All) was unchecking ALL materials in
validationResults, including invisible ones - Clicking "确认删除" (Confirm Deletion) processed ALL materials in
validationResults, not just visible ones - This caused User A to delete User B's materials that User A never saw!
Root Causes
1. "取消" Button (Line 420)
// ❌ WRONG: Clears ALL selected items
onClick={() => setSelectedItems(new Set())}
2. handleConfirmDeletion Function (Line 165)
// ❌ WRONG: Iterates ALL validation results
for (const result of validationResults) {
// Processes items user can't even see!
}
Data Flow
graph TB
subgraph "Backend"
A[validationResults<br/>1000 items] --> B[User Filter<br/>currentUsername]
end
subgraph "Frontend Display"
B --> C[filteredResults<br/>100 items visible]
C --> D[Table Display]
end
subgraph "Bug Behavior (BEFORE FIX)"
E[取消 Button] --> F[Clears selectedItems<br/>for ALL 1000 items ❌]
G[确认删除 Button] --> H[Processes ALL 1000 items ❌]
H --> I[Deletes User B's data ❌]
end
subgraph "Fixed Behavior (AFTER FIX)"
E2[取消 Button] --> F2[Clears only visible<br/>100 items ✅]
G2[确认删除 Button] --> H2[Processes only<br/>100 items ✅]
H2 --> I2[Only affects User A ✅]
end
Solution
Fix 1: "取消" Button - Only Uncheck Visible Items
File: src/renderer/src/pages/CleanerPage.tsx:419-432
<button
onClick={() => {
// Only uncheck items that are visible in filteredResults
const visibleCodes = new Set(filteredResults.map((r) => r.materialCode))
setSelectedItems((prev) => {
const newSet = new Set(prev)
for (const code of visibleCodes) {
newSet.delete(code)
}
return newSet
})
}}
className="text-xs bg-white border border-slate-300 text-slate-700 px-2.5 py-1.5 rounded shadow-sm hover:bg-slate-50 flex items-center gap-1"
>
<Square size={14} className="text-slate-400" /> 取消
</button>
What Changed:
- Before:
setSelectedItems(new Set())- clears everything - After: Iterates through
filteredResultsand removes only visible items fromselectedItems - Preserves selections for items not currently visible (e.g., other users' data)
Fix 2: handleConfirmDeletion - Only Process Visible Items (Non-Admin)
File: src/renderer/src/pages/CleanerPage.tsx:158-222
const handleConfirmDeletion = async () => {
// For non-admin users, only process visible filtered results
// For admin users, process all validation results
const resultsToProcess = isAdmin ? validationResults : filteredResults
if (resultsToProcess.length === 0) return alert('没有可处理的数据')
const materialsToUpsert: { materialCode: string; managerName: string }[] = []
const materialsToDelete: string[] = []
const missingManager: string[] = []
for (const result of resultsToProcess) {
// ... rest of processing logic
}
// ...
}
What Changed:
- Before:
for (const result of validationResults)- processes all 1000 items - After:
for (const result of resultsToProcess)where:Admin→ processesvalidationResults(all items)User→ processes onlyfilteredResults(visible items)
Testing Scenarios
Scenario 1: User Unchecks Own Data Only
Setup:
- User A logs in (non-Admin)
- 100 materials visible (assigned to User A)
- 900 materials invisible (assigned to other users)
- All 1000 materials are initially checked
Actions:
- User A clicks "取消"
- Table shows all checkboxes unchecked
Expected:
- ✅ User A's 100 materials are unchecked
- ✅ Other users' 900 materials remain checked (not affected)
Verification:
// Before fix: selectedItems.size === 0
// After fix: selectedItems.size === 900 (other users' items still checked)
Scenario 2: User Confirms Deletion
Setup:
- User A logs in (non-Admin)
- User A unchecks 50 of their 100 materials
- 50 items checked (User A's)
- 900 items checked (other users')
Actions:
- User A clicks "确认删除"
- Confirm dialog shows: "写入/更新 50 条记录"
Expected:
- ✅ Only User A's 50 materials are upserted to database
- ✅ Other users' 900 materials are NOT touched
- ✅ No materials are deleted (since other users' items aren't processed)
Scenario 3: Admin Behavior Unchanged
Setup:
- Admin logs in
- All 1000 materials visible
- All filtered by selected managers
Actions:
- Admin clicks "取消" → all visible items unchecked
- Admin clicks "确认删除" → processes all filtered items
Expected:
- ✅ Admin behavior unchanged (can manage all data)
- ✅ Admin can still filter by managers and process filtered results
Security & Scope Implications
Before Fix (Vulnerability)
flowchart LR
UserA[User A] --> Sees[Sees 100 items]
UserB[User B] --> Sees2[Sees 900 items]
Sees --> Clicks[Clicks 取消 + 确认删除]
Clicks --> Deletes[Deletes ALL 1000 items ❌]
Deletes --> Impact[User B loses data ❌]
After Fix (Secure)
flowchart LR
UserA[User A] --> Sees[Sees 100 items]
UserB[User B] --> Sees2[Sees 900 items]
Sees --> Clicks[Clicks 取消 + 确认删除]
Clicks --> Deletes[Deletes 100 items ✅]
Sees2 --> Independent[User B's data independent ✅]
Deletes --> Safe[User scope isolation ✅]
Code Changes Summary
File: src/renderer/src/pages/CleanerPage.tsx
| Line | Change | Description |
|---|---|---|
| 419-432 | Modified "取消" button | Only uncheck visible filteredResults |
| 158-222 | Modified handleConfirmDeletion |
Use resultsToProcess based on isAdmin |
Variables Used
validationResults: All materials from backend (1000 items)filteredResults: Materials after user/manager filtering (100 items for User A)selectedItems: Set of checked material codesisAdmin: Boolean, true for Admin userscurrentUsername: Current logged-in username
Verification Steps
-
Test as User A:
# Login as user1 npm run dev # Navigate to CleanerPage # Verify only user1's materials are visible # Click "取消" → only visible items unchecked # Check selectedItems size = other users' checked items -
Test as User B:
# Login as user2 # Verify user1's changes didn't affect user2's data # All user2's materials should still be intact -
Test as Admin:
# Login as admin # Verify can still see and manage all materials # "取消" and "确认删除" work on all filtered results
Related Files
- Implementation:
src/renderer/src/pages/CleanerPage.tsx - Related:
src/main/ipc/validation-handler.ts(backend matching logic) - Related:
docs/user-override-match-feature.md(user override matching)
Future Improvements
- Add Confirmation Dialog for Scope: Show user how many items will be affected
- Add Audit Logging: Log which user modified which materials
- Add Warning for Large Operations: Warn if user is about to delete many items
- Backend Validation: Add backend check to prevent cross-user data modification
Document End