authorize('customers.impersonate'); $operator = Auth::guard('operator')->user(); // Addressed by CUSTOMER uuid, not user: `users` has no uuid column, and // naming the integer key in a URL is what R11 forbids. The portal end // resolves the account the same way this end would have. return redirect()->away(URL::temporarySignedRoute( 'impersonate.enter', now()->addSeconds(self::WINDOW), ['customer' => $customer->uuid, 'operator' => $operator->uuid], )); } /** The portal end of the handover. Signed, and good exactly once. */ public function enter(Request $request, string $customer, string $operator): RedirectResponse { abort_unless($request->hasValidSignature(), 403); // Single use: the signature's own hash is the key, and it lives exactly // as long as the link could. Redis, not the database — the marker is as // short-lived as the link and should not need tidying up. $marker = 'impersonate:'.hash('sha256', (string) $request->query('signature')); abort_unless(Cache::add($marker, true, self::WINDOW), 403); $target = Customer::where('uuid', $customer)->firstOrFail()->ensureUser(); $who = Operator::where('uuid', $operator)->firstOrFail(); Auth::guard('web')->login($target); session(['impersonator_id' => $who->id]); return redirect()->route('dashboard'); } /** Back out of the customer's portal. The operator session was never touched. */ public function leave(): RedirectResponse { session()->forget('impersonator_id'); Auth::guard('web')->logout(); return redirect()->to(\App\Support\AdminArea::home()); } }