Files

166 lines
5.8 KiB
Markdown

# Payment Security Fixes
## Overview
Security enhancements have been added to the payment flow in `app/Http/Livewire/Pay.php` to prevent tampering with front-end properties via browser developer tools or by modifying Livewire component state.
## Fixes Applied
### 1. Balance Limit Check Now Blocks Execution
**Location:** `Pay.php`, `checkBalanceLimits()` method
**What was wrong:**
- The method set `isPaymentLimitErrorModalOpen = true` but returned `true`
- `doTransfer()` didn't check the return value, so execution continued
- A user could tamper with the check by modifying `isPaymentLimitErrorModalOpen` in the browser console
**What was fixed:**
- `checkBalanceLimits()` now returns a boolean: `true` if limits OK, `false` if exceeded
- `doTransfer()` now checks the return value: `if (!$limitsOk) { return; }`
- If limits are exceeded, the transaction is NOT saved and a security warning is logged
**Test Scenario:**
```javascript
// Before fix: User could bypass by setting in browser console:
Livewire.find('pay-component').isPaymentLimitErrorModalOpen = false;
// And payment would still be saved!
// After fix: Even if user tampersthis property, the backend check prevents saving
```
### 2. Amount Validation
**Location:** `Pay.php`, `doTransfer()` method
**What was added:**
```php
// Security: Validate amount is a valid positive number
if (!is_numeric($amount) || $amount <= 0) {
$warningMessage = 'Security: Invalid payment amount (must be positive number)';
return $this->logAndReport(...);
}
```
**Prevents:**
- Negative amounts (e.g., `amount = -50`)
- Zero amount (e.g., `amount = 0`)
- Non-numeric values (e.g., `amount = 'abc'`)
### 3. FromAccountId Validation
**Location:** `Pay.php`, `doTransfer()` method (added first)
**What was added:**
```php
// Security: Verify fromAccountId is an integer (prevent tampering)
if (!is_int($fromAccountId) || $fromAccountId <= 0) {
$warningMessage = 'Security: Invalid From account ID';
return $this->logAndReport(...);
}
```
**Prevents:**
- Non-integer account IDs
- Negative or zero account IDs
- String values passed as account ID
**Note:** This validation happens BEFORE the ownership check, catching obvious tampering early.
### 4. ToAccountId Validation
**Location:** `Pay.php`, `doTransfer()` method
**What was added:**
```php
// Security: Verify toAccountId is an integer (prevent tampering)
if (!is_int($toAccountId) || $toAccountId <= 0) {
$warningMessage = 'Security: Invalid To account ID';
return $this->logAndReport(...);
}
```
**Prevents:**
- Same issues as FromAccountId validation
### 5. Existing Ownership Check (Still in Place)
**Location:** `Pay.php`, `doTransfer()` method
This was already in place and remains:
```php
$accountsInfo = collect($transactionController->getAccountsInfo());
if (!$accountsInfo->contains('id', $fromAccountId)) {
$warningMessage = 'Unauthorized payment attempt: illegal access of From account';
return $this->logAndReport(...);
}
```
**Why this is important:**
- Even though the browser interface only shows accounts the user owns, a user could tamper with Livewire to send a `fromAccountId` for an account they don't own
- This backend check verifies the FromAccountId belongs to the active session profile
- The check happens AFTER type validation but BEFORE the transaction is saved
## Security Log Messages
All security violations are logged with a `[TRANSFER]` prefix. To check for security violations:
```bash
grep "\[TRANSFER\].*Security" storage/logs/laravel-*.log
```
Example log entries:
```
[2026-06-06 14:30:45] local.WARNING: [TRANSFER] Security: Invalid payment amount (must be positive number)
[2026-06-06 14:31:12] local.WARNING: [TRANSFER] Security: Invalid From account ID
[2026-06-06 14:32:33] local.WARNING: [TRANSFER] Security: Invalid To account ID
```
## Validation Order
The checks happen in this order in `doTransfer()`:
1. **Permission check** - Does user have right to make payments?
2. **Amount validation** - Is amount numeric and positive?
3. **FromAccountId type check** - Is it a positive integer?
4. **ToAccountId type check** - Is it a positive integer?
5. **Account existence check** - Do both accounts exist in DB?
6. **Account ownership check** - Does FromAccountId belong to active profile?
7. **Accountable check** - Are both account holders not removed?
8. **Same account check** - Are From and To different?
9. **Transaction type check** - Is type allowed?
10. **Balance limits check** - Does payment exceed limits?
If ANY check fails, execution stops and the transaction is NOT saved.
## What's NOT Fixed (By Design)
These are intentionally NOT checked in the backend because they're enforced by the database schema:
- Transaction immutability - Transactions cannot be modified/deleted (DB user permissions)
- Account balance accuracy - Balance is calculated from immutable transactions (MySQL window functions)
- Transaction type validity - Enforced by foreign key constraint
## Testing
**Manual Testing Instructions:**
1. Make a normal payment to verify it works
2. Open browser console and try:
```javascript
// Try to set negative amount
Livewire.find('pay-component-id').set('amount', -50);
// Click Pay button - should be blocked with security log entry
```
3. Try to inject invalid account IDs:
```javascript
Livewire.find('pay-component-id').set('toAccountId', -1);
// Should be blocked with security log entry
```
4. Check logs for security violations:
```bash
tail -f storage/logs/laravel-$(date +%Y-%m-%d).log | grep "\[TRANSFER\]"
```
## Related Code
- `PaymentSecurityTest.php` - Automated tests for security validations (if needed)
- `app/Http/Controllers/TransactionController.php` - API endpoint that also validates payments
- `app/Observers/TransactionObserver.php` - Clears caches on successful transaction creation