🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
727 lines
25 KiB
Plaintext
Executable File
727 lines
25 KiB
Plaintext
Executable File
NAME
|
|
session - RSX session management and authentication system
|
|
|
|
SYNOPSIS
|
|
Static session interface with lazy initialization and CLI mode support
|
|
|
|
DESCRIPTION
|
|
The RSX Session class provides a unified interface for session management
|
|
and authentication. Unlike Laravel's session() helper which returns a
|
|
session store, RSX uses static methods for direct access to session data.
|
|
|
|
Sessions are always persistent (365 days) - there is no "remember me"
|
|
option. The framework handles session creation, authentication, and
|
|
security automatically.
|
|
|
|
Key differences from Laravel:
|
|
- Laravel: session()->put('user_id', $id) or Auth::login($user)
|
|
- RSX: Session::set_user($user) or Session::set_user_id($user_id)
|
|
|
|
Benefits:
|
|
- Static interface accessible from anywhere
|
|
- Automatic session creation only when needed
|
|
- CSRF token management built-in
|
|
- Secure cookie settings by default
|
|
- CLI mode support (no database operations)
|
|
- No session overhead until actually used
|
|
|
|
LAZY INITIALIZATION
|
|
Critical Concept:
|
|
Sessions are NOT created until a method forces them to exist. Simply
|
|
calling Session::get_user_id() or Session::get_site_id() will NOT
|
|
create a session if none exists - they return null/0.
|
|
|
|
Session is created when:
|
|
- Session::set_user() or Session::set_user_id() is called
|
|
- Session::set_site() or Session::set_site_id() is called
|
|
- Session::get_session_id() is called
|
|
- Session::get_session() is called
|
|
|
|
Session is NOT created when:
|
|
- Session::get_user_id() - returns null if no session
|
|
- Session::get_site_id() - returns 0 if no session
|
|
- Session::get_user() - returns null if no session
|
|
- Session::get_site() - returns null if no session
|
|
- Session::is_logged_in() - returns false if no session
|
|
- Session::has_session() - returns false if no session
|
|
- Session::init() - only loads existing session, never creates
|
|
|
|
Example Flow:
|
|
// Page load - no session created yet
|
|
$user_id = Session::get_user_id(); // null, no session created
|
|
|
|
// User logs in - NOW session is created
|
|
Session::set_user_id(123); // Creates session with user_id=123
|
|
|
|
// Subsequent requests load the existing session
|
|
$user_id = Session::get_user_id(); // 123, loads from cookie
|
|
|
|
This lazy initialization prevents unnecessary database writes for
|
|
unauthenticated visitors who never log in or interact with site-specific
|
|
features.
|
|
|
|
CLI MODE
|
|
Behavior in CLI Context:
|
|
When running in CLI mode (artisan commands, tests), Session operates
|
|
in a degraded "mock" mode using static properties only:
|
|
|
|
- No database reads or writes
|
|
- No cookie operations
|
|
- No HTTP headers sent
|
|
- Session data stored in static properties only
|
|
- get_client_ip() returns "CLI"
|
|
|
|
Usage in CLI:
|
|
// Set user and site for command context
|
|
Session::set_user_id(123);
|
|
Session::set_site_id(456);
|
|
|
|
// All getter methods work normally
|
|
$user_id = Session::get_user_id(); // 123
|
|
$site_id = Session::get_site_id(); // 456
|
|
$logged_in = Session::is_logged_in(); // true
|
|
$has_session = Session::has_session(); // true
|
|
|
|
CLI mode automatically detects when running via php_sapi_name() === 'cli'
|
|
and requires external code to explicitly set user_id/site_id.
|
|
|
|
BASIC USAGE
|
|
Check Authentication:
|
|
use App\RSpade\Core\Session\Session;
|
|
|
|
// Check if user is logged in
|
|
if (Session::is_logged_in()) {
|
|
$user = Session::get_user();
|
|
$user_id = Session::get_user_id();
|
|
}
|
|
|
|
// Check if session exists at all
|
|
if (Session::has_session()) {
|
|
// Session record exists or CLI values set
|
|
}
|
|
|
|
Login User:
|
|
// Option 1: Pass user ID
|
|
Session::set_user_id(123);
|
|
|
|
// Option 2: Pass user model
|
|
$user = User_Model::find(123);
|
|
Session::set_user($user);
|
|
|
|
// Both options:
|
|
// - Create session if none exists
|
|
// - Regenerate session token (prevent session fixation)
|
|
// - Update user's last_login timestamp
|
|
// - Set secure cookie
|
|
|
|
Logout:
|
|
Session::logout();
|
|
// OR
|
|
Session::set_user(null);
|
|
|
|
Site Management:
|
|
// Get current site
|
|
$site_id = Session::get_site_id(); // Returns 0 if not set
|
|
$site = Session::get_site(); // Returns Site_Model or null
|
|
|
|
// Set site
|
|
Session::set_site_id(456);
|
|
// OR
|
|
Session::set_site($site_model);
|
|
|
|
CSRF Protection:
|
|
// Get CSRF token for forms
|
|
$token = Session::get_csrf_token();
|
|
|
|
// Verify submitted token
|
|
if (Session::verify_csrf_token($submitted_token)) {
|
|
// Token is valid
|
|
}
|
|
|
|
API REFERENCE
|
|
Authentication Methods:
|
|
Session::is_logged_in(): bool
|
|
Returns true if user_id is set, false otherwise.
|
|
Does NOT create session if none exists.
|
|
|
|
Session::has_session(): bool
|
|
Returns true if session exists (web mode) or if site_id/user_id
|
|
set (CLI mode). Does NOT create session if none exists.
|
|
|
|
User Methods:
|
|
Session::get_user_id(): int|null
|
|
Returns current user ID or null. Does NOT create session.
|
|
|
|
Session::get_user(): User_Model|null
|
|
Returns User_Model for current user or null. Caches result.
|
|
Does NOT create session.
|
|
|
|
Session::set_user(User_Model|int|null $user): void
|
|
Set current user. Pass null or 0 to logout.
|
|
CREATES session if none exists.
|
|
Regenerates session token on login (security).
|
|
|
|
Session::set_user_id(int|null $user_id): void
|
|
Convenience method, same as set_user().
|
|
CREATES session if none exists.
|
|
|
|
Session::logout(): void
|
|
Clears user_id from session. Same as set_user(null).
|
|
|
|
Site Methods:
|
|
Session::get_site_id(): int
|
|
Returns current site ID or 0. Does NOT create session.
|
|
|
|
Session::get_site(): Site_Model|null
|
|
Returns Site_Model for current site or null. Caches result.
|
|
Does NOT create session.
|
|
|
|
Session::set_site(Site_Model|int $site): void
|
|
Set current site ID. CREATES session if none exists.
|
|
|
|
Session::set_site_id(int $site_id): void
|
|
Convenience method, same as set_site().
|
|
CREATES session if none exists.
|
|
|
|
Site User Methods:
|
|
Session::get_site_user(): Site_User_Model|null
|
|
Returns Site_User_Model matching both current user_id and site_id,
|
|
or null if either is not set or no matching record exists.
|
|
Does NOT create session.
|
|
|
|
Session Access Methods:
|
|
Session::get_session_id(): int
|
|
Returns session record ID. CREATES session if none exists.
|
|
|
|
Session::get_session(): Session
|
|
Returns Session model. CREATES session if none exists.
|
|
|
|
CSRF Methods:
|
|
Session::get_csrf_token(): string|null
|
|
Returns CSRF token for current session or null.
|
|
Does NOT create session.
|
|
|
|
Session::verify_csrf_token(string $token): bool
|
|
Verifies submitted CSRF token using constant-time comparison.
|
|
Returns false if no session exists.
|
|
|
|
Administrative Methods:
|
|
Session::find_by_token(string $token): Session|null
|
|
Find active session by token (for API/external access).
|
|
|
|
Session::cleanup_expired(int $days = 365): int
|
|
Delete sessions older than specified days. Returns count deleted.
|
|
Run periodically via scheduled command.
|
|
|
|
Session::reset(): void
|
|
Logout and clear all session data. Marks session inactive.
|
|
Clears cookie.
|
|
|
|
CONTROLLER AUTHENTICATION
|
|
Using pre_dispatch:
|
|
class Admin_Controller extends Rsx_Controller_Abstract
|
|
{
|
|
public static function pre_dispatch(Request $request, array $params = [])
|
|
{
|
|
// Require authentication for all admin routes
|
|
if (!Session::is_logged_in()) {
|
|
return redirect('/login');
|
|
}
|
|
|
|
// Require specific user role
|
|
$user = Session::get_user();
|
|
if (!$user || $user->role !== 'admin') {
|
|
return redirect('/');
|
|
}
|
|
|
|
return null; // Continue to route
|
|
}
|
|
}
|
|
|
|
Application-wide Authentication:
|
|
In rsx/main.php:
|
|
|
|
public static function pre_dispatch(Request $request, array $params = [])
|
|
{
|
|
// Require auth for /app/* routes
|
|
if (str_starts_with($request->path(), 'app/')) {
|
|
if (!Session::is_logged_in()) {
|
|
return redirect('/login');
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
SECURITY
|
|
Automatic Security Features:
|
|
- Session tokens: 64-character cryptographically secure random
|
|
- Token regeneration on login prevents session fixation
|
|
- Constant-time token comparison prevents timing attacks
|
|
- Secure cookie flags: httponly, secure, samesite=Lax
|
|
- CSRF tokens generated automatically
|
|
- 365-day expiration with last_active tracking
|
|
|
|
Cookie Settings:
|
|
- Name: rsx_session
|
|
- HttpOnly: true (no JavaScript access)
|
|
- Secure: true (HTTPS only)
|
|
- SameSite: Lax (CSRF protection)
|
|
- Path: /
|
|
- Expires: 365 days from last activity
|
|
|
|
Session Fixation Prevention:
|
|
Session tokens are regenerated when:
|
|
- User logs in (set_user called with non-null value)
|
|
|
|
This prevents attackers from forcing a victim to use a
|
|
known session token.
|
|
|
|
EXAMPLES
|
|
Login Form Processing:
|
|
#[Route('/login')]
|
|
public static function login(Request $request, array $params = [])
|
|
{
|
|
if ($request->method() === 'POST') {
|
|
$email = $request->input('email');
|
|
$password = $request->input('password');
|
|
|
|
$user = User_Model::where('email', $email)->first();
|
|
|
|
if ($user && password_verify($password, $user->password)) {
|
|
// Login successful - this creates/regenerates session
|
|
Session::set_user($user);
|
|
|
|
Rsx::flash_success('Login successful!');
|
|
return redirect('/dashboard');
|
|
}
|
|
|
|
Rsx::flash_error('Invalid credentials');
|
|
return redirect('/login');
|
|
}
|
|
|
|
return view('auth/login');
|
|
}
|
|
|
|
Require Authentication:
|
|
public static function dashboard(Request $request, array $params = [])
|
|
{
|
|
// Check authentication at start of method
|
|
if (!Session::is_logged_in()) {
|
|
Rsx::flash_error('Please login to continue');
|
|
return redirect('/login');
|
|
}
|
|
|
|
$user = Session::get_user();
|
|
|
|
return view('dashboard/index', [
|
|
'user' => $user,
|
|
]);
|
|
}
|
|
|
|
Multi-tenant Site Selection:
|
|
public static function select_site(Request $request, array $params = [])
|
|
{
|
|
$site_id = $params['site_id'] ?? null;
|
|
|
|
// Verify user has access to this site
|
|
$site_user = Site_User_Model::where('user_id', Session::get_user_id())
|
|
->where('site_id', $site_id)
|
|
->first();
|
|
|
|
if (!$site_user) {
|
|
Rsx::flash_error('Access denied to this site');
|
|
return redirect('/sites');
|
|
}
|
|
|
|
// Set active site
|
|
Session::set_site_id($site_id);
|
|
|
|
Rsx::flash_success('Switched to ' . $site_user->site->name);
|
|
return redirect('/dashboard');
|
|
}
|
|
|
|
CSRF Protection in Forms:
|
|
In controller:
|
|
return view('forms/example', [
|
|
'csrf_token' => Session::get_csrf_token(),
|
|
]);
|
|
|
|
In Blade view:
|
|
<form method="POST">
|
|
<input type="hidden" name="csrf_token" value="{{ $csrf_token }}">
|
|
<!-- form fields -->
|
|
</form>
|
|
|
|
In POST handler:
|
|
$submitted = $request->input('csrf_token');
|
|
if (!Session::verify_csrf_token($submitted)) {
|
|
Rsx::flash_error('Invalid CSRF token');
|
|
return redirect()->back();
|
|
}
|
|
|
|
CLI Command with User Context:
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use App\RSpade\Core\Session\Session;
|
|
|
|
class ProcessUserData extends Command
|
|
{
|
|
protected $signature = 'data:process {user_id}';
|
|
|
|
public function handle()
|
|
{
|
|
$user_id = $this->argument('user_id');
|
|
|
|
// Set CLI session context
|
|
Session::set_user_id($user_id);
|
|
|
|
// Now all code that checks Session::get_user_id() will work
|
|
$this->process_data();
|
|
|
|
$this->info('Processed data for user ' . $user_id);
|
|
}
|
|
|
|
private function process_data()
|
|
{
|
|
// This works because we set user_id above
|
|
$user_id = Session::get_user_id();
|
|
// ... process data for user ...
|
|
}
|
|
}
|
|
|
|
RSX VS LARAVEL
|
|
Session Access:
|
|
Laravel:
|
|
session()->put('key', 'value');
|
|
$value = session()->get('key');
|
|
session()->forget('key');
|
|
|
|
RSX:
|
|
Session::set_user_id(123);
|
|
$user_id = Session::get_user_id();
|
|
Session::logout();
|
|
|
|
RSX uses typed methods instead of generic key/value storage.
|
|
|
|
Authentication:
|
|
Laravel:
|
|
Auth::login($user);
|
|
Auth::logout();
|
|
$user = Auth::user();
|
|
$id = Auth::id();
|
|
if (Auth::check()) { }
|
|
|
|
RSX:
|
|
Session::set_user($user);
|
|
Session::logout();
|
|
$user = Session::get_user();
|
|
$id = Session::get_user_id();
|
|
if (Session::is_logged_in()) { }
|
|
|
|
RSX combines session and auth into one unified interface.
|
|
|
|
Session Creation:
|
|
Laravel:
|
|
Session starts automatically on every request via middleware.
|
|
|
|
RSX:
|
|
Session only created when explicitly needed via set_user(),
|
|
set_site(), get_session_id(), or get_session(). Getter methods
|
|
like get_user_id() do NOT create sessions.
|
|
|
|
This prevents unnecessary database writes for unauthenticated traffic.
|
|
|
|
CSRF Protection:
|
|
Laravel:
|
|
@csrf in Blade templates
|
|
$request->session()->token()
|
|
Automatic verification via middleware
|
|
|
|
RSX:
|
|
Session::get_csrf_token() in controllers
|
|
Session::verify_csrf_token($token) manual verification
|
|
Manual implementation gives explicit control
|
|
|
|
Remember Me:
|
|
Laravel:
|
|
Auth::login($user, $remember = true);
|
|
Optional remember functionality
|
|
|
|
RSX:
|
|
All sessions are persistent (365 days)
|
|
No "remember me" checkbox needed
|
|
|
|
TROUBLESHOOTING
|
|
Session Not Persisting Across Requests:
|
|
Problem: Session data is lost between requests
|
|
|
|
Solutions:
|
|
- Check that cookies are enabled in browser
|
|
- Verify HTTPS is being used (secure flag requires HTTPS)
|
|
- Check session token in cookie: rsx_session
|
|
- Verify session record exists in database
|
|
- Check last_active timestamp is being updated
|
|
|
|
User Logged Out Unexpectedly:
|
|
Problem: User is logged out without calling logout()
|
|
|
|
Solutions:
|
|
- Check session expiration (365 days by default)
|
|
- Verify session.active = true in database
|
|
- Check for code calling Session::reset() or Session::logout()
|
|
- Verify session token hasn't changed
|
|
|
|
CSRF Token Validation Failing:
|
|
Problem: verify_csrf_token() returns false
|
|
|
|
Solutions:
|
|
- Ensure token is being submitted in POST data
|
|
- Check session exists (get_csrf_token returns null if no session)
|
|
- Verify token wasn't regenerated between form display and submit
|
|
- Check for session fixation prevention regenerating token
|
|
|
|
CLI Commands Not Working:
|
|
Problem: Session methods return null/0 in artisan commands
|
|
|
|
Solution:
|
|
- Explicitly set user_id/site_id in CLI context:
|
|
Session::set_user_id($user_id);
|
|
Session::set_site_id($site_id);
|
|
|
|
- CLI mode requires manual context setting, does not load from DB
|
|
|
|
Session Created for Anonymous Users:
|
|
Problem: Every visitor gets a session record
|
|
|
|
Solution:
|
|
- Only call methods that CREATE sessions when needed:
|
|
CREATES: set_user(), set_site(), get_session_id(), get_session()
|
|
NO CREATE: get_user_id(), get_site_id(), is_logged_in()
|
|
|
|
- Don't call get_session_id() or get_session() in templates/layouts
|
|
- Use has_session() to check without creating
|
|
|
|
GARBAGE COLLECTION
|
|
Automatic Session Cleanup:
|
|
Sessions are automatically cleaned up via scheduled task that runs
|
|
daily at 3 AM. No manual configuration required.
|
|
|
|
Cleanup Rules:
|
|
- Logged-in sessions (login_user_id set): Deleted after 365 days
|
|
- Anonymous sessions (login_user_id null): Deleted after 14 days
|
|
|
|
The cleanup uses last_active timestamp to determine session age.
|
|
Sessions are permanently deleted from the database.
|
|
|
|
Implementation:
|
|
Service: Session_Cleanup_Service::cleanup_sessions()
|
|
Schedule: Daily at 3 AM (via #[Schedule] attribute)
|
|
Automatic: No cron configuration needed
|
|
|
|
Manual Trigger:
|
|
To manually run cleanup outside the schedule:
|
|
|
|
php artisan rsx:task:run Session_Cleanup_Service cleanup_sessions
|
|
|
|
This is useful for:
|
|
- Testing cleanup logic
|
|
- Emergency cleanup when disk space is low
|
|
- One-time cleanup after changing retention policies
|
|
|
|
SESSION MANAGEMENT
|
|
Multi-Device Session Tracking:
|
|
RSX tracks all active sessions for each user, allowing users to view
|
|
and manage their active sessions across devices.
|
|
|
|
Get All Sessions for User:
|
|
// Get all active sessions for current user
|
|
$sessions = Session::get_sessions_for_user();
|
|
|
|
// Get sessions for a specific user (admin use)
|
|
$sessions = Session::get_sessions_for_user($login_user_id);
|
|
|
|
Each session includes:
|
|
- id: Session ID
|
|
- ip_address: Client IP
|
|
- user_agent: Raw user agent string
|
|
- user_agent_parsed: Parsed browser/OS/device info
|
|
- device_summary: Human-readable string (e.g., "Chrome on Windows")
|
|
- location: Geo location (null until geo lookup implemented)
|
|
- last_active: Last activity timestamp
|
|
- created_at: Session creation timestamp
|
|
- is_current: Boolean indicating if this is the current session
|
|
|
|
Get Current Session Info:
|
|
$info = Session::get_current_session_info();
|
|
// Returns same structure as above, or null if not logged in
|
|
|
|
Terminate Sessions:
|
|
// Terminate a specific session (cannot terminate current)
|
|
$success = Session::terminate_session($session_id);
|
|
|
|
// Terminate all other sessions (keep current)
|
|
$count = Session::terminate_all_other_sessions();
|
|
|
|
// Terminate all sessions for a user (admin action)
|
|
$count = Session::terminate_all_sessions_for_user($login_user_id);
|
|
|
|
// Terminate all except one specific session
|
|
$count = Session::terminate_all_sessions_for_user($login_user_id, $except_id);
|
|
|
|
Use Cases:
|
|
- "Sign out all other devices" button
|
|
- Security page showing active sessions
|
|
- Admin forcing user logout after password change
|
|
- Detecting suspicious login locations
|
|
|
|
LOGIN HISTORY
|
|
The Login_History class provides audit logging of all login attempts.
|
|
This is separate from sessions - it records the attempt itself, not
|
|
the resulting session.
|
|
|
|
Class: App\RSpade\Core\Session\Login_History
|
|
|
|
Recording Login Attempts:
|
|
use App\RSpade\Core\Session\Login_History;
|
|
|
|
// Record successful login
|
|
Login_History::record_success($login_user_id, $email);
|
|
|
|
// Record failed login
|
|
Login_History::record_failure(
|
|
$email,
|
|
Login_History::STATUS_FAILED_PASSWORD,
|
|
'Optional reason details',
|
|
$login_user_id // Optional, if user exists
|
|
);
|
|
|
|
Status Constants:
|
|
Login_History::STATUS_SUCCESS - Successful login
|
|
Login_History::STATUS_FAILED_PASSWORD - Wrong password
|
|
Login_History::STATUS_FAILED_2FA - 2FA verification failed
|
|
Login_History::STATUS_FAILED_LOCKED - Account locked
|
|
Login_History::STATUS_FAILED_DISABLED - Account disabled
|
|
Login_History::STATUS_FAILED_NOT_FOUND - User not found
|
|
|
|
Retrieving Login History:
|
|
// Get recent login history for a user
|
|
$history = Login_History::get_history_for_user($login_user_id, $limit = 10);
|
|
|
|
Each record includes:
|
|
- id: Record ID
|
|
- email: Email attempted
|
|
- ip_address: Client IP
|
|
- user_agent: Raw user agent string
|
|
- user_agent_parsed: Parsed browser/OS/device info
|
|
- location: Geo location (null until implemented)
|
|
- status: Status constant value
|
|
- status_label: Human-readable status (e.g., "Success", "Failed - Invalid Password")
|
|
- failure_reason: Optional failure details
|
|
- created_at: Timestamp
|
|
|
|
Rate Limiting Support:
|
|
// Count failed attempts for an email in time window
|
|
$count = Login_History::get_failed_attempts_count($email, $minutes = 15);
|
|
|
|
// Count failed attempts from an IP
|
|
$count = Login_History::get_failed_attempts_count_by_ip($ip, $minutes = 15);
|
|
|
|
Example rate limiting:
|
|
$failed = Login_History::get_failed_attempts_count($email, 15);
|
|
if ($failed >= 5) {
|
|
return response_error(Ajax::ERROR_GENERIC, 'Too many failed attempts');
|
|
}
|
|
|
|
Integration with Login Flow:
|
|
public static function login(Request $request, array $params = [])
|
|
{
|
|
$email = $params['email'];
|
|
$password = $params['password'];
|
|
|
|
$user = Login_User_Model::where('email', $email)->first();
|
|
|
|
if (!$user) {
|
|
Login_History::record_failure($email, Login_History::STATUS_FAILED_NOT_FOUND);
|
|
return response_error(Ajax::ERROR_VALIDATION, ['email' => 'Invalid credentials']);
|
|
}
|
|
|
|
if (!password_verify($password, $user->password)) {
|
|
Login_History::record_failure(
|
|
$email,
|
|
Login_History::STATUS_FAILED_PASSWORD,
|
|
null,
|
|
$user->id
|
|
);
|
|
return response_error(Ajax::ERROR_VALIDATION, ['email' => 'Invalid credentials']);
|
|
}
|
|
|
|
// Success
|
|
Login_History::record_success($user->id, $email);
|
|
Session::set_login_user_id($user->id);
|
|
|
|
return ['redirect' => Rsx::Route('Dashboard_Index_Action')];
|
|
}
|
|
|
|
USER AGENT PARSING
|
|
The User_Agent class parses user agent strings into human-readable
|
|
components for display in session/login history UIs.
|
|
|
|
Class: App\RSpade\Core\Session\User_Agent
|
|
|
|
Basic Usage:
|
|
use App\RSpade\Core\Session\User_Agent;
|
|
|
|
$parsed = User_Agent::parse($user_agent_string);
|
|
// Returns:
|
|
// [
|
|
// 'browser' => 'Chrome',
|
|
// 'os' => 'Windows',
|
|
// 'device' => 'Desktop',
|
|
// 'summary' => 'Chrome on Windows'
|
|
// ]
|
|
|
|
// Quick summary only
|
|
$summary = User_Agent::get_summary($user_agent_string);
|
|
// Returns: "Chrome on Windows"
|
|
|
|
Detected Browsers:
|
|
Chrome, Firefox, Safari, Edge, Opera, Brave, Vivaldi, Internet Explorer
|
|
|
|
Detected Operating Systems:
|
|
Windows, macOS, iOS, iPadOS, Android, Linux, Chrome OS, Ubuntu, Fedora
|
|
|
|
Detected Device Types:
|
|
Desktop, Mobile, Tablet
|
|
|
|
Notes:
|
|
- This is a simple parser for display purposes
|
|
- Not intended for feature detection or security decisions
|
|
- For comprehensive parsing, use a dedicated library like WhichBrowser
|
|
- Location fields are placeholders for future geo-IP integration
|
|
|
|
LOCATION GEOLOCATION (FUTURE)
|
|
The login_history and session info structures include location fields
|
|
that are currently null. These are placeholders for future integration
|
|
with an IP geolocation service.
|
|
|
|
Fields reserved:
|
|
- location_city: City name
|
|
- location_region: State/province
|
|
- location_country: ISO country code (2 characters)
|
|
|
|
When implemented, the system will:
|
|
- Look up IP address on login/session creation
|
|
- Cache results to avoid repeated lookups
|
|
- Populate location fields automatically
|
|
|
|
Candidate services for future integration:
|
|
- MaxMind GeoIP2 (local database, most accurate)
|
|
- ip-api.com (free tier available)
|
|
- ipinfo.io (generous free tier)
|
|
|
|
SEE ALSO
|
|
rsx:man routing - Type-safe URL generation
|
|
rsx:man model_fetch - Ajax ORM with security
|
|
rsx:man rsx_architecture - RSX application structure |