Files
rspade_system/app/RSpade/man/session.txt
root f6fac6c4bc Fix bin/publish: copy docs.dist from project root
Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 02:08:33 +00:00

526 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
Expired Session Cleanup:
Sessions older than 365 days should be deleted periodically.
Create scheduled command:
php artisan make:command CleanupSessions
In handle() method:
$deleted = Session::cleanup_expired(365);
$this->info("Deleted $deleted expired sessions");
Schedule in app/Console/Kernel.php:
$schedule->command('sessions:cleanup')->daily();
The cleanup_expired() method deletes sessions where last_active
is older than the specified number of days.
SEE ALSO
rsx:man routing - Type-safe URL generation
rsx:man model_fetch - Ajax ORM with security
rsx:man rsx_architecture - RSX application structure