Files
rspade_system/app/RSpade/man/session.txt
2025-12-11 05:26:44 +00:00

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