authorize('customers.impersonate'); $operator = Auth::guard('operator')->user(); // impersonate.enter carries no domain of its own — a portal route, // reached wherever the portal already answers. Its SIGNATURE is // computed over the full absolute URL: UrlGenerator::signedRoute() // hashes the generated URL string, host included, and // hasCorrectSignature() re-hashes $request->url() — the ACTUAL request // URL — on the way back. Left alone, "the generated URL" means // whichever host issued THIS request: the console's, once it has an // exclusive hostname of its own. RestrictAdminHost then 404s every // non-console route reached through the console's exclusive host, // including this one — so the link would die before the customer ever // saw it, the exact cross-host failure this controller exists to fix. // // Forced to the portal's own configured root instead, derived from // config('app.url') rather than hardcoded, and restored immediately // after: nothing else generated later in this request should inherit // the override. $portalRoot = rtrim((string) config('app.url'), '/'); URL::forceRootUrl($portalRoot); try { $signed = URL::temporarySignedRoute( 'impersonate.enter', now()->addSeconds(self::WINDOW), // 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. ['customer' => $customer->uuid, 'operator' => $operator->uuid], ); } finally { URL::forceRootUrl(null); } return redirect()->away($signed); } /** 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()); } }