Files
rspade_system/app/RSpade/man/session.txt
root 9ebcc359ae Fix code quality violations and enhance ROUTE-EXISTS-01 rule
Implement JQHTML function cache ID system and fix bundle compilation
Implement underscore prefix for system tables
Fix JS syntax linter to support decorators and grant exception to Task system
SPA: Update planning docs and wishlists with remaining features
SPA: Document Navigation API abandonment and future enhancements
Implement SPA browser integration with History API (Phase 1)
Convert contacts view page to SPA action
Convert clients pages to SPA actions and document conversion procedure
SPA: Merge GET parameters and update documentation
Implement SPA route URL generation in JavaScript and PHP
Implement SPA bootstrap controller architecture
Add SPA routing manual page (rsx:man spa)
Add SPA routing documentation to CLAUDE.md
Phase 4 Complete: Client-side SPA routing implementation
Update get_routes() consumers for unified route structure
Complete SPA Phase 3: PHP-side route type detection and is_spa flag
Restore unified routes structure and Manifest_Query class
Refactor route indexing and add SPA infrastructure
Phase 3 Complete: SPA route registration in manifest
Implement SPA Phase 2: Extract router code and test decorators
Rename Jqhtml_Component to Component and complete SPA foundation setup

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 17:48:15 +00:00

536 lines
18 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
SEE ALSO
rsx:man routing - Type-safe URL generation
rsx:man model_fetch - Ajax ORM with security
rsx:man rsx_architecture - RSX application structure