fix(teams): clear stale current_team_id when membership ends
Reset the user's persisted current_team_id when they are removed from a team, when their team is deleted, or when refreshSession finds no team left, so a dangling reference is never restored on next login.
This commit is contained in:
parent
12498b4b86
commit
2b92fb86b5
5 changed files with 56 additions and 1 deletions
|
|
@ -50,6 +50,7 @@ public function handle(Team $team, User $user): ?Team
|
|||
->get()
|
||||
->each(function (User $member) use ($team): void {
|
||||
$member->teams()->detach($team);
|
||||
$member->clearStoredTeamIfMatches($team->id);
|
||||
DB::table('sessions')->where('user_id', $member->id)->delete();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ public function remove()
|
|||
DB::transaction(function () use ($teamId): void {
|
||||
$this->member->teams()->detach($teamId);
|
||||
RevokeUserTeamTokens::forUserTeam($this->member, $teamId);
|
||||
$this->member->clearStoredTeamIfMatches($teamId);
|
||||
});
|
||||
// Clear cache for the removed user - both old and new key formats
|
||||
Cache::forget("team:{$this->member->id}");
|
||||
|
|
|
|||
|
|
@ -401,6 +401,21 @@ public function resolveStoredTeam(): ?Team
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the persisted active team when it points to the given team.
|
||||
*
|
||||
* Called when the user is removed from a team (or the team is deleted) so a
|
||||
* stale current_team_id can never be trusted after the fact. Read paths
|
||||
* already re-validate membership; this is defense-in-depth that clears the
|
||||
* dangling value at the source event instead of relying on self-healing.
|
||||
*/
|
||||
public function clearStoredTeamIfMatches(int $teamId): void
|
||||
{
|
||||
if ($this->current_team_id === $teamId) {
|
||||
$this->forceFill(['current_team_id' => null])->saveQuietly();
|
||||
}
|
||||
}
|
||||
|
||||
public function role(): ?string
|
||||
{
|
||||
if (data_get($this, 'pivot')) {
|
||||
|
|
|
|||
|
|
@ -581,8 +581,13 @@ function refreshSession(?Team $team = null): void
|
|||
if (! $team) {
|
||||
// The user has no team left (e.g. just deleted their current team and
|
||||
// belongs to no other): clear the stale session reference instead of
|
||||
// dereferencing null.
|
||||
// dereferencing null, and drop the persisted choice so it is not
|
||||
// restored on next login.
|
||||
session()->forget('currentTeam');
|
||||
$user = Auth::user();
|
||||
if ($user && ! is_null($user->current_team_id)) {
|
||||
$user->forceFill(['current_team_id' => null])->saveQuietly();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Actions\Team\DeleteTeam;
|
||||
use App\Livewire\SelectTeam;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
|
|
@ -128,6 +129,38 @@ function userWithTwoTeams(): array
|
|||
expect($response->headers->get('Location'))->not->toBe(route('team.select'));
|
||||
});
|
||||
|
||||
it('clears the stored team when the member is removed from it', function () {
|
||||
[$user, , $second] = userWithTwoTeams();
|
||||
$user->update(['current_team_id' => $second->id]);
|
||||
|
||||
// Simulate the removal event (Team\Member::remove detaches then clears).
|
||||
$user->teams()->detach($second->id);
|
||||
$user->clearStoredTeamIfMatches($second->id);
|
||||
|
||||
expect($user->fresh()->current_team_id)->toBeNull();
|
||||
});
|
||||
|
||||
it('keeps the stored team when the member is removed from a different team', function () {
|
||||
[$user, $personal, $second] = userWithTwoTeams();
|
||||
$user->update(['current_team_id' => $second->id]);
|
||||
|
||||
$user->teams()->detach($personal->id);
|
||||
$user->clearStoredTeamIfMatches($personal->id);
|
||||
|
||||
expect($user->fresh()->current_team_id)->toBe($second->id);
|
||||
});
|
||||
|
||||
it('clears the stored team for members when their team is deleted', function () {
|
||||
[$owner, , $shared] = userWithTwoTeams();
|
||||
$member = User::factory()->create();
|
||||
$member->teams()->attach($shared, ['role' => 'member']);
|
||||
$member->update(['current_team_id' => $shared->id]);
|
||||
|
||||
app(DeleteTeam::class)->handle($shared->fresh(), $owner);
|
||||
|
||||
expect($member->fresh()->current_team_id)->toBeNull();
|
||||
});
|
||||
|
||||
it('bounces users who already have an active team away from the select screen', function () {
|
||||
[$user, , $second] = userWithTwoTeams();
|
||||
$user->update(['current_team_id' => $second->id]);
|
||||
|
|
|
|||
Loading…
Reference in a new issue