Concierge::Sessions - Session Management System
Version: 0.9.0
Concierge::Sessions is a comprehensive session management system for Perl applications, providing flexible storage backends, sliding window expiration, and application-controlled data storage.
Sessions enable applications to track user actions and maintain state across multiple operations. Keep session data in memory for fast access, persist to storage when needed, and maintain continuity between user interactions whether in online services, CLI tools, games, time-billing systems, or any application that needs to track state over time.
Features
- Application-controlled data storage: Store any serializable data structure in sessions
- In-memory performance: Fast access to state and configuration
- Optional persistence: Session tracks changes, saves when App tells it to
- Single-session enforcement: Enforces one active session per user
- Sliding window expiration: Sessions auto-extend when users are active
- Indefinite sessions: Application-wide sessions that never expire
- Multiple backends: database (production), file (testing/small user population)
- Modern Perl: v5.36+ with contemporary best practices
- Service layer pattern: Non-fatal errors with descriptive messages
Installation
Standard CPAN installation:
cpanm Concierge::Sessions
# OR
perl Makefile.PL
make
make test
make install
Requirements
- Perl 5.36 or later
- DBI (for SQLite backend)
- DBD::SQLite (for SQLite backend)
- JSON::PP or JSON
- Time::HiRes
- File::Spec
- Test2::V0 (for testing)
Quick Start
use Concierge::Sessions;
# Create session manager
my $sessions = Concierge::Sessions->new(
backend => 'database',
storage_dir => '/var/app/sessions',
);
# Create user session
my $result = $sessions->new_session(
user_id => 'user123',
data => {
cart => [],
preferences => { theme => 'dark' },
},
);
if ($result->{success}) {
my $session = $result->{session};
my $session_id = $session->session_id();
# Read session data
my $data_result = $session->get_data();
my $data = $data_result->{value};
# Update session data
$data->{cart} = ['item1', 'item2'];
$session->set_data($data);
# Save changes (extends session timeout)
$session->save();
}
# Retrieve session later
my $retrieved = $sessions->get_session($session_id);
Usage Patterns
Sliding Window Expiration
Sessions automatically extend when save() is called:
my $session = $sessions->new_session(
user_id => 'user123',
session_timeout => 3600, # 1 hour
)->{session};
# User activity - save() extends the session
$session->save(); # Session now expires 1 hour from now
Active users stay logged in; inactive users expire automatically.
Indefinite Sessions
For application-wide state that never expires:
my $app_session = $sessions->new_session(
user_id => 'application_main',
session_timeout => 'indefinite',
data => {
metrics => { requests_processed => 0 },
subsystems => { database => 'connected' },
},
)->{session};
# This session never expires
$app_session->is_expired(); # Always returns false
Data Access
All data operations work with the entire data field:
# Get entire data structure
my $result = $session->get_data();
my $data = $result->{value};
# Modify the data structure
$data->{username} = 'alice';
$data->{preferences}{language} = 'en';
$data->{cart} = [@items];
# Replace entire data field (marks session as dirty)
$session->set_data($data);
# Persist to backend (also extends session timeout)
$session->save();
Session Lifecycle
# Create
my $result = $sessions->new_session(user_id => 'user123');
my $session = $result->{session};
# Check session status
if ($session->is_valid()) {
# Session is active and not expired
}
# Retrieve later
my $retrieved = $sessions->get_session($session->session_id());
# Delete when done
$sessions->delete_session($session->session_id());
Backend Selection
# Database backend (production, high performance)
my $sessions = Concierge::Sessions->new(
backend => 'database',
storage_dir => '/var/app/sessions',
);
# File backend (testing, human-readable)
my $sessions = Concierge::Sessions->new(
backend => 'file',
storage_dir => '/tmp/sessions',
);
API Overview
Concierge::Sessions (Factory)
All factory methods return hashrefs with {success => 1|0, ...}:
$sessions->new_session(user_id => ..., data => ...)
# Returns: {success => 1, session => $session_object}
$sessions->get_session($session_id)
# Returns: {success => 1, session => $session_object}
$sessions->delete_session($session_id)
# Returns: {success => 1, message => '...'}
$sessions->delete_user_session($user_id)
# Returns: {success => 1, deleted_count => 3}
$sessions->cleanup_sessions()
# Returns: {success => 1, deleted_count => 5}
Concierge::Sessions::Session (Session Object)
# Data methods
$session->get_data() # {success => 1, value => $data}
$session->set_data($data) # {success => 1}
$session->save() # {success => 1}
# Status checks
$session->is_valid() # 1 if active and not expired
$session->is_active() # 1 if state is 'active'
$session->is_expired() # 1 if past expiration time
$session->is_dirty() # 1 if unsaved changes exist
# Accessors
$session->session_id() # Session ID string
$session->created_at() # Creation timestamp
$session->expires_at() # Expiration timestamp
$session->last_updated() # Last update timestamp
$session->storage_backend() # Backend class name
$session->status() # {state => 'active', dirty => 0}
Design Principles
Explicit Persistence
No automatic saving on scope exit. You control when data is persisted:
$session->set_data($new_data); # Changes in memory only
$session->is_dirty(); # True - changes not saved
$session->save(); # Persists to backend
$session->is_dirty(); # False - saved
Single-Session Enforcement
Only one active session per user. Creating a new session invalidates old ones:
my $session1 = $sessions->new_session(user_id => 'user123')->{session};
my $session2 = $sessions->new_session(user_id => 'user123')->{session};
# $session1 is now deleted (enforced by backend)
Service Layer Pattern
All methods return consistent result hashrefs. The module only dies/croaks during initialization if the backend cannot be initialized. All other failures are non-fatal:
# Factory methods return hashrefs
my $result = $sessions->new_session(user_id => 'user123');
if ($result->{success}) {
my $session = $result->{session};
# Use session
} else {
warn "Failed to create session: " . $result->{message};
}
# Session object methods also return hashrefs
my $save_result = $session->save();
if ($save_result->{success}) {
# Session saved successfully
} else {
warn "Failed to save session: " . $save_result->{message};
}
Default Session Timeout: If not specified, sessions timeout after 3600 seconds (1 hour).
Use session_timeout => 'indefinite' for sessions that never expire.
Documentation
Full API documentation is available in the module POD:
perldoc Concierge::Sessions
perldoc Concierge::Sessions::Session
Testing
# Run all tests
prove -lv t/
# Run specific test file
prove -lv t/01-sessions-manager.t
Test coverage: 75 tests across 4 test files, all passing.
Examples
See the examples/ directory for usage examples:
04-indefinite-session.pl- Application-wide session demonstration
Performance
- SQLite backend: 4,000-5,000 operations per second
- File backend: ~1,000 operations per second
Benchmarks performed on typical hardware with default settings.
License
Artistic License 2.0 - See LICENSE file for details.
This is the same license as Perl itself.
Author
Bruce Van Allen bva@cruzio.com
See Also
- DBI - Database interface
- JSON::PP - JSON handling
- Time::HiRes - High resolution time
Version History
Version 0.9.0 2026-02-13
- Added META provides (resolves CPANTS meta_yml_has_provides)
- Added SECURITY.md with vulnerability reporting policy
(resolves CPANTS has_security_doc, security_doc_contains_contact)
- Added CONTRIBUTING.md with contribution guidelines
(resolves CPANTS has_contributing_doc)
- Added xt/pod-no-nonascii.t author test to guard against non-ASCII in POD
- Fixed stale POD versions in Base.pm, Session.pm, SQLite.pm, File.pm
- Bumped all module versions to v0.9.0
Version 0.8.9 2026-02-13
- Removed non-ASCII characters from POD in Files.pm
Version 0.8.8 2026-02-13
- Switched session ID generation from Crypt::URandom to Crypt::PRNG
(random_bytes), aligning with Concierge::Auth::Generators and reducing
overall Concierge suite dependencies
- CVE-2026-2439: Insecure session ID generation via uuidgen/rand fallback
was fixed in v0.8.5; this entry documents the assigned CVE
Version 0.8.7 2026-02-13
- Fixed CPAN tester timeout failures: session expiry in installation
tests now mocked via direct SQLite update (no sleep). Real-time
timeout tests moved to xt/ (author tests only, skipped under
AUTOMATED_TESTING).
Version 0.8.6 2026-02-12
- Rebuilt tarball with GNU tar (fixes PaxHeader issue on CPAN)
Version 0.8.5 2026-02-12
- Security: replaced insecure session ID generation (uuidgen/rand fallback)
with cryptographically secure random IDs via Crypt::URandom (160-bit entropy)
- Added Crypt::URandom as a dependency
- Further widened sliding window test timing margins for slow platforms
Version 0.8.4 2026-02-12
- Fixed integration test timing margins for slow platforms (Windows/Strawberry Perl)
Version 0.8.3 2026-02-11
- Fixed session expiration tests that were timing out too fast
Version 0.8.2 2026-02-11
- Improved documentation
Version 0.8.1 (Current)
- Backend parameter names: 'database' and 'file' (case-insensitive)
- Method name updates: delete_user_session, cleanup_sessions
- Version alignment across all modules
Version 0.7.0
- Initial release as Concierge::Sessions
- Sliding window session expiration
- Indefinite session support
- Multiple backend support (database, file)
- 75 tests, all passing
- Production-ready