NAME
Concierge - Service layer orchestrator for authentication, sessions, and user data
VERSION
v0.5.6
SYNOPSIS
use Concierge;
# Open an existing desk (created by Concierge::Setup)
my $desk = Concierge->open_desk('./desk');
my $concierge = $desk->{concierge};
# Register a user
$concierge->add_user({
user_id => 'alice',
moniker => 'Alice',
email => 'alice@example.com',
password => 'secret123',
});
# Log in -- returns a Concierge::User object
my $login = $concierge->login_user({
user_id => 'alice',
password => 'secret123',
});
my $user = $login->{user};
# User object provides direct access
say $user->moniker; # "Alice"
say $user->session_id; # random hex string
say $user->is_logged_in; # 1
# Restore user from a cookie on next request
my $restore = $concierge->restore_user($user->user_key);
my $same_user = $restore->{user};
# Log out
$concierge->logout_user($user->session_id);
DESCRIPTION
Concierge coordinates three component modules behind a single API:
Concierge::Auth -- password authentication (Argon2)
Concierge::Sessions -- session management (SQLite or file backends)
Concierge::Users -- user data storage (SQLite, YAML, or CSV/TSV backends)
Applications interact only with Concierge and the Concierge::User objects it returns. The component modules are never exposed directly.
Desks
A desk is a storage directory containing the configuration and data files for all three components. Use Concierge::Setup to create a desk, then open_desk() to load it at runtime.
User Participation Levels
Concierge provides three graduated levels of user participation, each returning a Concierge::User object:
- Visitor --
admit_visitor() -
Assigned a unique identifier only. No session, no stored data. Suitable for anonymous tracking (e.g., cookies).
- Guest --
checkin_guest() -
Assigned an identifier and a session. Can store temporary data (e.g., a shopping cart). No authentication or persistent user record.
- Logged-in user --
login_user() -
Authenticated with credentials. Has a session, persistent user data, and full access to the User object's data methods.
A guest can be converted to a logged-in user with login_guest(), transferring any session data accumulated during the guest session.
User Keys
Each active user (guest or logged-in) is tracked by a user_key -- a random token stored in the concierge's user_keys mapping alongside the user's user_id and session_id. This mapping is persisted to user_keys.json in the desk directory and synchronized against active sessions when the desk is opened.
Return Values
All methods return a hashref with at least success (0 or 1) and message:
# Success
{ success => 1, message => '...', ... }
# Failure
{ success => 0, message => 'error description' }
Methods never croak during normal operation. The one exception is open_desk(), which croaks if the desk directory does not exist.
Architecture
Concierge ships with three identity core components:
- Concierge::Auth -- credential storage and verification
- Concierge::Sessions -- session lifecycle and persistence
- Concierge::Users -- user records with configurable field schemas
These three are tightly orchestrated: a single login_user() call authenticates via Auth, retrieves a record from Users, and creates a session through Sessions. This coordination is the purpose of Concierge -- applications interact with the Concierge API and the Concierge::User objects it returns, not with the components directly.
The identity core is designed to be sufficient on its own, but the component pattern it follows -- backend abstraction, setup-time configuration, and Concierge-level orchestration -- is intentionally replicable. An application that needs to manage organizations, assets, or other structured data alongside its users could introduce an additional component under the Concierge:: namespace, following the same conventions: a setup() method for configuration, pluggable backends, and integration through Concierge's desk configuration.
Each identity core component can also be substituted. The interfaces are designed so an application could replace Argon2 with LDAP or OAuth in Auth, swap SQLite for PostgreSQL in Sessions or Users, or provide an entirely custom backend -- by supplying a module that conforms to the same method contract.
METHODS
Desk Management
open_desk
my $result = Concierge->open_desk($desk_location);
my $concierge = $result->{concierge};
Opens an existing desk directory created by Concierge::Setup. Reads the configuration file, instantiates all component modules, loads the user_keys mapping, and runs session cleanup.
Croaks if $desk_location is not an existing directory.
Returns { success => 1, concierge => $obj } on success.
User Lifecycle
admit_visitor
my $result = $concierge->admit_visitor();
my $user = $result->{user}; # Concierge::User (visitor)
Creates a visitor with a generated identifier. No session is created and no data is stored.
checkin_guest
my $result = $concierge->checkin_guest(\%session_opts);
my $user = $result->{user}; # Concierge::User (guest)
Creates a guest with a generated identifier and a session. The optional %session_opts hashref may include timeout (in seconds; defaults to 1800).
login_user
my $result = $concierge->login_user(\%credentials, \%session_opts);
my $user = $result->{user}; # Concierge::User (logged-in)
Authenticates user_id and password from %credentials, retrieves the user's data record, creates a session, and returns a fully-equipped User object. If the user already has an active session, the previous session is replaced.
restore_user
my $result = $concierge->restore_user($user_key);
my $user = $result->{user}; # Concierge::User (guest or logged-in)
Reconstructs a User object from a user_key (typically stored in a cookie or URL token). Looks up the key in the concierge mapping, validates the session, and determines whether the user is a guest or logged-in user.
Logged-in users are restored with their full user data snapshot and backend closures. Guests are restored with their session only.
If the session has expired, the stale mapping entry is cleaned up and the method returns failure. The application can then redirect to login or create a new guest as appropriate.
Returns { success => 1, user => $user } on success. Guest restores also include is_guest => 1.
login_guest
my $result = $concierge->login_guest(\%credentials, $guest_user_key);
my $user = $result->{user}; # Concierge::User (logged-in)
Converts a guest to a logged-in user. Authenticates with %credentials, transfers any data from the guest's session to the new session, then deletes the guest session and removes the guest's user_key mapping.
logout_user
my $result = $concierge->logout_user($session_id);
Deletes the session and removes the user_key mapping entry.
Admin Operations
add_user
my $result = $concierge->add_user(\%user_input);
Registers a new user. %user_input must include user_id, moniker, and password. Any additional fields (email, phone, application- defined fields, etc.) are stored in the Users component. The password is stored separately in the Auth component and never reaches the user data store.
If password validation fails, the Users record is rolled back.
remove_user
my $result = $concierge->remove_user($user_id);
Removes the user from all components: Users, Auth, Sessions, and the user_keys mapping. Attempts all deletions; the response includes deleted_from (arrayref) and warnings (arrayref, if any component deletion failed).
verify_user
my $result = $concierge->verify_user($user_id);
Checks whether $user_id exists in both Auth and Users components. Returns verified => 1 only if present in both. Includes exists_in_auth and exists_in_users flags, and a warning if the user exists in one component but not the other.
list_users
# IDs only
my $result = $concierge->list_users($filter, \%options);
my @ids = @{ $result->{user_ids} };
# With full data
my $result = $concierge->list_users('', { include_data => 1 });
my %users = %{ $result->{users} };
Returns user IDs from the Users component. $filter is a string passed through to Concierge::Users. With include_data => 1, fetches each user's full record into a users hash keyed by user_id. With fields => [...], returns only the specified fields per user.
get_user_data
my $result = $concierge->get_user_data($user_id, @fields);
my $data = $result->{user};
Retrieves user data from the Users component. If @fields is provided, returns only those fields; otherwise returns all fields.
update_user_data
my $result = $concierge->update_user_data($user_id, \%updates);
Updates the user's record in the Users component. The user_id and password fields are filtered out and cannot be changed through this method.
Password Operations
verify_password
my $result = $concierge->verify_password($user_id, $password);
Checks whether $password is correct for $user_id. Returns success => 1 if the password matches.
reset_password
my $result = $concierge->reset_password($user_id, $new_password);
Sets a new password for an existing user. The application is responsible for verifying the user's identity before calling this method.
PARAMETER FILTERS
Concierge uses Params::Filter to enforce data segregation at method boundaries:
$auth_data_filter-- extracts onlyuser_idandpassword$user_data_filter-- extracts everything exceptpassword$session_data_filter-- extractsuser_idplus non-credential fields$user_update_filter-- excludesuser_idandpasswordfrom updates
These ensure that credentials never leak into user data stores and that identity fields cannot be changed via update operations.
SEE ALSO
Concierge::Setup -- desk creation and configuration
Concierge::User -- user objects returned by lifecycle methods
Concierge::Auth, Concierge::Sessions, Concierge::Users -- component modules
AUTHOR
Bruce Van Allen <bva@cruzio.com>
LICENSE
This module is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0.