CluPilotCloud/app/Http/Middleware/TouchLoginSession.php

75 lines
2.7 KiB
PHP

<?php
namespace App\Http\Middleware;
use App\Models\LoginSession;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\Response;
/**
* Keeps a session row pointing at the session it describes.
*
* Laravel rotates the session id on every sign-in and on every regenerate().
* A row that stored the id once stops matching within a request or two — and
* the list would then show sessions that cannot be ended and hide the one the
* reader is sitting in. The uuid inside the payload is what survives; the id is
* refreshed from it here.
*
* Also the only thing that keeps "last seen" honest. Without it the list
* reports the moment somebody signed in, which for a browser left open for a
* fortnight is the least useful timestamp available.
*
* Written at most once a minute. This runs on every authenticated request, and
* a write per request would turn a page of thumbnails into forty updates of the
* same row.
*/
class TouchLoginSession
{
private const THROTTLE_SECONDS = 60;
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
if (! $request->hasSession()) {
return $response;
}
$uuid = $request->session()->get(LoginSession::KEY);
if (! is_string($uuid) || $uuid === '') {
return $response;
}
$current = $request->session()->getId();
// A bare update rather than a model round-trip: this is on the path of
// every authenticated request, and the row is not needed, only moved.
DB::table('login_sessions')
->where('uuid', $uuid)
->where(function ($q) use ($current) {
// A CHANGED id is never throttled. Both sign-in paths raise the
// Login event before calling session()->regenerate(), so the row
// is written against an id that is dead a moment later. Held
// back by the throttle, the row would point at nothing for the
// first minute of every session — the one it describes missing
// from the list, and "end this one" deleting a session that no
// longer exists.
$q->where('session_id', '!=', $current)
->orWhereNull('session_id')
->orWhereNull('last_seen_at')
->orWhere('last_seen_at', '<=', now()->subSeconds(self::THROTTLE_SECONDS));
})
->update([
'session_id' => $current,
'ip_address' => $request->ip(),
'last_seen_at' => now(),
'updated_at' => now(),
]);
return $response;
}
}