$user->getAuthIdentifier(), 'token' => $token, 'user_agent' => $request?->userAgent(), 'ip_address' => $request?->ip(), 'last_used_at' => now(), ]); session([self::SESSION_TOKEN_KEY => $token]); return $session; } /** * Revokes every OTHER active session for $user — the current one * (matched by the token in the CURRENT session payload) is left * alone, matching Laravel's own logoutOtherDevices() semantics * (there just isn't a password to re-verify against here — this is a * passwordless account, so revocation is simply "every row that * isn't the one making this request"). * * Known, deliberately accepted gap: this requires only a currently * valid session, not a freshly-completed login — so anyone holding * an already-authenticated session (e.g. someone who sits down at an * account left logged in on a shared/public PC) can use this to * evict the real owner's OTHER sessions just as easily as the real * owner could use it to evict an intruder's. A stricter version would * require a fresh OTP re-verification (e.g. within the last few * minutes) before allowing this call. Left as-is for now — revisit if * this turns out to matter in practice, rather than building * abuse-resistance against a threat model nobody's confirmed is real * for this storefront. */ public function revokeOtherSessions(Authenticatable $user): int { $currentToken = session(self::SESSION_TOKEN_KEY); return UserSession::query() ->where('user_id', $user->getAuthIdentifier()) ->whereNull('revoked_at') ->when($currentToken, fn ($query) => $query->where('token', '!=', $currentToken)) ->update(['revoked_at' => now()]); } /** * Revokes EVERY session for $user, current one included — for a * "this account may be compromised" response, not a routine logout. */ public function revokeAllSessions(Authenticatable $user): int { return UserSession::query() ->where('user_id', $user->getAuthIdentifier()) ->whereNull('revoked_at') ->update(['revoked_at' => now()]); } /** * @return UserSession|null null if the CURRENT session has no * recorded token at all (e.g. a session predating this feature, or * one Auth::login() established outside UserOtpService) — treated * as valid by EnsureSessionNotRevoked rather than rejected, since * there's nothing to have been revoked. */ public function currentSession(): ?UserSession { $token = session(self::SESSION_TOKEN_KEY); if (! $token) { return null; } return UserSession::where('token', $token)->first(); } }