Add login history tracking and session management features

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-12-11 05:26:44 +00:00
parent dd6c17f923
commit 18928c8678
10 changed files with 881 additions and 142 deletions

View File

@@ -8,6 +8,7 @@ use App\RSpade\Core\Manifest\Manifest;
use App\RSpade\Core\Models\Login_User_Model;
use App\RSpade\Core\Models\Site_Model;
use App\RSpade\Core\Models\User_Model;
use App\RSpade\Core\Session\User_Agent;
/**
* Session model - handles both authentication sessions and static session management
@@ -792,4 +793,159 @@ class Session extends Rsx_System_Model_Abstract
{
return self::$_request_site_id_override !== null;
}
// =========================================================================
// SESSION MANAGEMENT METHODS
// =========================================================================
/**
* Get all active sessions for a login user
* Returns formatted session info including device/location parsing
*
* @param int|null $login_user_id If null, uses current logged-in user
* @return array Array of session info
*/
public static function get_sessions_for_user(?int $login_user_id = null): array
{
if ($login_user_id === null) {
$login_user_id = self::get_login_user_id();
}
if (empty($login_user_id)) {
return [];
}
$current_session_id = self::has_session() ? self::$_session?->id : null;
return static::where('login_user_id', $login_user_id)
->where('active', true)
->orderBy('last_active', 'desc')
->get()
->map(function ($session) use ($current_session_id) {
$parsed_ua = User_Agent::parse($session->user_agent);
return [
'id' => $session->id,
'ip_address' => $session->ip_address,
'user_agent' => $session->user_agent,
'user_agent_parsed' => $parsed_ua,
'device_summary' => $parsed_ua['summary'],
'location' => self::_format_session_location($session),
'last_active' => $session->last_active,
'created_at' => $session->created_at,
'is_current' => $session->id === $current_session_id,
];
})
->toArray();
}
/**
* Terminate a specific session by ID
* Cannot terminate the current session (use logout() instead)
*
* @param int $session_id
* @return bool True if session was terminated, false if not found or is current
*/
public static function terminate_session(int $session_id): bool
{
self::init();
// Don't allow terminating current session
if (self::$_session && self::$_session->id === $session_id) {
return false;
}
$affected = static::where('id', $session_id)
->where('active', true)
->update(['active' => false]);
return $affected > 0;
}
/**
* Terminate all sessions for the current user except the current one
*
* @return int Number of sessions terminated
*/
public static function terminate_all_other_sessions(): int
{
self::init();
$login_user_id = self::get_login_user_id();
if (empty($login_user_id)) {
return 0;
}
$query = static::where('login_user_id', $login_user_id)
->where('active', true);
// Exclude current session if we have one
if (self::$_session) {
$query->where('id', '!=', self::$_session->id);
}
return $query->update(['active' => false]);
}
/**
* Terminate all sessions for a specific login user
* Useful for admin actions or password changes
*
* @param int $login_user_id
* @param int|null $except_session_id Optional session ID to exclude
* @return int Number of sessions terminated
*/
public static function terminate_all_sessions_for_user(int $login_user_id, ?int $except_session_id = null): int
{
$query = static::where('login_user_id', $login_user_id)
->where('active', true);
if ($except_session_id !== null) {
$query->where('id', '!=', $except_session_id);
}
return $query->update(['active' => false]);
}
/**
* Get information about the current session
* Returns formatted info with device/location parsing
*
* @return array|null Session info or null if not logged in
*/
public static function get_current_session_info(): ?array
{
self::init();
if (empty(self::$_session)) {
return null;
}
$parsed_ua = User_Agent::parse(self::$_session->user_agent);
return [
'id' => self::$_session->id,
'ip_address' => self::$_session->ip_address,
'user_agent' => self::$_session->user_agent,
'user_agent_parsed' => $parsed_ua,
'device_summary' => $parsed_ua['summary'],
'location' => self::_format_session_location(self::$_session),
'last_active' => self::$_session->last_active,
'created_at' => self::$_session->created_at,
'is_current' => true,
];
}
/**
* Format location string from session (placeholder for future geo lookup)
*
* @param Session $session
* @return string|null
*/
private static function _format_session_location($session): ?string
{
// Future: Implement geo lookup based on IP
// For now, return null (location not available)
return null;
}
}