fix: impersonation UX — banner contrast, route blocking, nav filtering
- Banner: white elevated button for contrast, fixed 48px height, layout top padding offset so content isn't obscured - Middleware: allow GET me/profile (viewing), block mutations only; add auth/refresh to blocked routes - Navigation: hide Platform section during impersonation; hide org-dependent items when impersonated user has no organisation - Test: add read-only routes allowed test, auth/refresh blocked test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,15 +14,14 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
class HandleImpersonation
|
||||
{
|
||||
/**
|
||||
* Routes that are blocked during impersonation.
|
||||
* These are prefix-matched against the request path (without api/v1 prefix).
|
||||
* Routes that are blocked during impersonation (all methods).
|
||||
* Prefix-matched against the request path (without api/v1 prefix).
|
||||
*/
|
||||
private const SENSITIVE_ROUTE_PREFIXES = [
|
||||
'auth/password',
|
||||
private const BLOCKED_ROUTE_PREFIXES = [
|
||||
'auth/logout',
|
||||
'auth/refresh',
|
||||
'auth/mfa',
|
||||
'auth/trusted-devices',
|
||||
'me/profile',
|
||||
'me/change-password',
|
||||
'me/change-email',
|
||||
'admin/impersonate',
|
||||
@@ -101,7 +100,12 @@ class HandleImpersonation
|
||||
$path = $request->path();
|
||||
$path = preg_replace('#^api/v1/#', '', $path);
|
||||
|
||||
foreach (self::SENSITIVE_ROUTE_PREFIXES as $prefix) {
|
||||
// Block profile mutations but allow GET (viewing)
|
||||
if (str_starts_with($path, 'me/profile') && $request->method() !== 'GET') {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (self::BLOCKED_ROUTE_PREFIXES as $prefix) {
|
||||
if (str_starts_with($path, $prefix)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -391,6 +391,7 @@ class AdminImpersonationTest extends TestCase
|
||||
['POST', '/api/v1/auth/mfa/setup/totp'],
|
||||
['GET', '/api/v1/auth/mfa/status'],
|
||||
['GET', '/api/v1/auth/trusted-devices'],
|
||||
['POST', '/api/v1/auth/refresh'],
|
||||
];
|
||||
|
||||
foreach ($sensitiveRoutes as [$method, $url]) {
|
||||
@@ -406,6 +407,23 @@ class AdminImpersonationTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function test_read_only_routes_allowed_during_impersonation(): void
|
||||
{
|
||||
Sanctum::actingAs($this->superAdmin);
|
||||
|
||||
$this->postJson(
|
||||
"/api/v1/admin/impersonate/{$this->targetUser->id}",
|
||||
$this->startPayload(),
|
||||
)->assertOk();
|
||||
|
||||
// GET /auth/me should be allowed (profile viewing)
|
||||
$response = $this->withHeader('X-Impersonate-User', $this->targetUser->id)
|
||||
->getJson('/api/v1/auth/me');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('data.id', $this->targetUser->id);
|
||||
}
|
||||
|
||||
public function test_ip_change_terminates_session(): void
|
||||
{
|
||||
Sanctum::actingAs($this->superAdmin);
|
||||
|
||||
Reference in New Issue
Block a user