fix(backups): revalidate S3 storage on scheduled backup submit

Check the selected S3 storage against the database at submit time so
stale Livewire state cannot schedule backups with storage that was
reassigned or marked unusable after the component mounted.
This commit is contained in:
Andras Bacsai
2026-05-23 21:06:22 +02:00
parent a4d75ff0e2
commit 33e172ac24
2 changed files with 44 additions and 1 deletions
@@ -2,6 +2,7 @@
namespace App\Livewire\Project\Database;
use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ServiceDatabase;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
@@ -50,7 +51,13 @@ class CreateScheduledBackup extends Component
$this->validate();
if ($this->saveToS3) {
if (is_null($this->s3StorageId) || ! $this->definedS3s->contains('id', $this->s3StorageId)) {
$s3StorageExists = ! is_null($this->s3StorageId)
&& S3Storage::where('team_id', currentTeam()->id)
->where('is_usable', true)
->whereKey($this->s3StorageId)
->exists();
if (! $s3StorageExists) {
$this->dispatch('error', 'Please select a valid S3 storage to enable S3 backups.');
return;
@@ -84,6 +84,42 @@ it('rejects an S3 storage not owned by the current team', function () {
expect(ScheduledDatabaseBackup::count())->toBe(0);
});
it('rejects an S3 storage that is reassigned after the component is mounted', function () {
$database = createDatabaseForScheduledBackupTest($this->team);
$s3 = createS3StorageForTeam($this->team);
$component = Livewire::test(CreateScheduledBackup::class, ['database' => $database])
->set('frequency', '0 0 * * *')
->set('saveToS3', true)
->set('s3StorageId', $s3->id);
$s3->update(['team_id' => Team::factory()->create()->id]);
$component
->call('submit')
->assertDispatched('error');
expect(ScheduledDatabaseBackup::count())->toBe(0);
});
it('rejects an S3 storage that becomes unusable after the component is mounted', function () {
$database = createDatabaseForScheduledBackupTest($this->team);
$s3 = createS3StorageForTeam($this->team);
$component = Livewire::test(CreateScheduledBackup::class, ['database' => $database])
->set('frequency', '0 0 * * *')
->set('saveToS3', true)
->set('s3StorageId', $s3->id);
$s3->update(['is_usable' => false]);
$component
->call('submit')
->assertDispatched('error');
expect(ScheduledDatabaseBackup::count())->toBe(0);
});
it('creates a scheduled backup with a valid team-owned S3 storage', function () {
$database = createDatabaseForScheduledBackupTest($this->team);
$s3 = createS3StorageForTeam($this->team);