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>
526 lines
18 KiB
Plaintext
Executable File
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 |