fix(team): resolve stored team on deletion and impersonation
Use resolveStoredTeam() instead of teams()->first() when picking the next active team after a team deletion or when an admin switches into a user's account, so a valid stored preference wins over an arbitrary first team. DeleteTeam now returns null when the deletion leaves the owner with multiple teams, deferring to the selection screen instead of guessing. refreshSession also stops persisting current_team_id while impersonating, so viewing another user's account no longer overwrites their real last-active team.
This commit is contained in:
parent
f511921895
commit
e52390ec03
4 changed files with 60 additions and 5 deletions
|
|
@ -60,7 +60,12 @@ public function handle(Team $team, User $user): ?Team
|
|||
|
||||
$team->delete();
|
||||
|
||||
return $user->teams()->first();
|
||||
// Resolve the next active team the same way login does: the user's
|
||||
// stored choice when still valid, or their sole remaining team.
|
||||
// Returns null for a multi-team user whose active team was just
|
||||
// deleted, so refreshSession sends them to the selection screen
|
||||
// instead of silently dropping them into an arbitrary first team.
|
||||
return User::query()->find($user->id)?->resolveStoredTeam();
|
||||
});
|
||||
|
||||
Cache::forget("user:{$user->id}:team:{$team->id}");
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public function back()
|
|||
if (session('impersonating')) {
|
||||
session()->forget('impersonating');
|
||||
$user = User::find(0);
|
||||
$team_to_switch_to = $user->teams->first();
|
||||
$team_to_switch_to = $user->resolveStoredTeam() ?? $user->teams->first();
|
||||
Auth::login($user);
|
||||
refreshSession($team_to_switch_to);
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ public function switchUser(int $user_id)
|
|||
if (! $user) {
|
||||
abort(404);
|
||||
}
|
||||
$team_to_switch_to = $user->teams->first();
|
||||
$team_to_switch_to = $user->resolveStoredTeam() ?? $user->teams->first();
|
||||
Auth::login($user);
|
||||
refreshSession($team_to_switch_to);
|
||||
|
||||
|
|
|
|||
|
|
@ -602,9 +602,11 @@ function refreshSession(?Team $team = null): void
|
|||
});
|
||||
session(['currentTeam' => $team]);
|
||||
|
||||
// Persist the active team so it can be restored after logout/login.
|
||||
// Persist the active team so it can be restored after logout/login — but
|
||||
// never while an admin is impersonating, so viewing another user's account
|
||||
// does not overwrite that user's real last-active team.
|
||||
$user = Auth::user();
|
||||
if ($user && $user->current_team_id !== $team->id) {
|
||||
if ($user && ! session('impersonating') && $user->current_team_id !== $team->id) {
|
||||
$user->current_team_id = $team->id;
|
||||
$user->saveQuietly();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,6 +183,54 @@ function userWithTwoTeams(): array
|
|||
expect($user->fresh()->current_team_id)->toBe($personal->id);
|
||||
});
|
||||
|
||||
it('returns the sole remaining team when the deleting owner has one team left', function () {
|
||||
[$owner, $personal, $shared] = userWithTwoTeams();
|
||||
$owner->update(['current_team_id' => $shared->id]);
|
||||
|
||||
$next = app(DeleteTeam::class)->handle($shared->fresh(), $owner);
|
||||
|
||||
expect($next?->id)->toBe($personal->id);
|
||||
});
|
||||
|
||||
it('returns null (picker) when the deleting owner still has multiple teams left', function () {
|
||||
[$owner, , $shared] = userWithTwoTeams();
|
||||
$third = Team::factory()->create(['show_boarding' => false]);
|
||||
$owner->teams()->attach($third, ['role' => 'owner']);
|
||||
$owner->update(['current_team_id' => $shared->id]);
|
||||
|
||||
// Deleting the active team leaves personal + third: ambiguous, so no team is
|
||||
// chosen silently and refreshSession(null) routes to the selection screen.
|
||||
$next = app(DeleteTeam::class)->handle($shared->fresh(), $owner->fresh());
|
||||
|
||||
expect($next)->toBeNull();
|
||||
});
|
||||
|
||||
it('keeps the active team when the deleted team was not the active one', function () {
|
||||
[$owner, $personal, $shared] = userWithTwoTeams();
|
||||
$third = Team::factory()->create(['show_boarding' => false]);
|
||||
$owner->teams()->attach($third, ['role' => 'owner']);
|
||||
$owner->update(['current_team_id' => $personal->id]);
|
||||
|
||||
// Deleting a non-active team must not move the owner off their active team.
|
||||
$next = app(DeleteTeam::class)->handle($shared->fresh(), $owner->fresh());
|
||||
|
||||
expect($next?->id)->toBe($personal->id);
|
||||
});
|
||||
|
||||
it('does not persist current_team_id while impersonating', function () {
|
||||
[$user, , $second] = userWithTwoTeams();
|
||||
$user->update(['current_team_id' => $second->id]);
|
||||
$this->actingAs($user);
|
||||
session(['impersonating' => true]);
|
||||
|
||||
// Viewing a user's account switches the session team but must never
|
||||
// overwrite that user's stored last-active team.
|
||||
refreshSession($user->teams->first());
|
||||
|
||||
expect(data_get(session('currentTeam'), 'id'))->toBe($user->teams->first()->id)
|
||||
->and($user->fresh()->current_team_id)->toBe($second->id);
|
||||
});
|
||||
|
||||
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