Malachi.Auth.SessionManager (malachi v0.8.13)

View Source

Advanced session management with IP binding and trusted proxy support.

Extends the basic session system with:

  • IP Binding: Binds sessions to source IP address to prevent hijacking
  • Trusted Proxies: Supports CIDR ranges of trusted proxies (NAT, load balancers)
  • Activity Tracking: Tracks last activity for dynamic timeout
  • Session Revocation: Allows manual revocation of specific sessions or all sessions for a user

Configuration

  • session_timeout_seconds - Session expiration time (default: 3600 = 1 hour)
  • session_ip_binding - Enables IP binding (default: true)
  • session_ua_binding - Enables User-Agent binding (default: false)
  • trusted_proxy_ranges - List of CIDR ranges for trusted proxies (default: [], nothing is trusted)

Trusted Proxies

The default is an empty list, so no range is trusted and no session is exempted from the binding check (which itself runs unless session_ip_binding is false). The ranges below are an example to copy, not a default: without this setting a client whose public address changes (mobile, a NAT pool that rotates) fails the check on its next request.

For environments with NAT or corporate proxies, configure trusted ranges:

config :malachi, trusted_proxy_ranges: [
  "10.0.0.0/8",        # RFC1918 - Class A private network
  "172.16.0.0/12",     # RFC1918 - Class B private network
  "192.168.0.0/16"     # RFC1918 - Class C private network
]

Sessions created from IPs in these ranges will have IP binding disabled.

Summary

Functions

Creates a new session with optional IP binding.

Lists active sessions.

Revokes all sessions for a user.

Revokes a specific session.

Functions

create_session(username, permissions, client_ip, user_agent \\ "")

Creates a new session with optional IP binding.

Parameters

  • username - Username
  • permissions - List of permissions (:admin, :produce, :consume)
  • client_ip - Client IP address (tuple)
  • user_agent - User-Agent string stored on the session (compared on validation when session_ua_binding is on)

Returns

  • {:ok, token} - Token of the created session

Examples

iex> SessionManager.create_session("admin", [:admin], {192, 168, 1, 1}, "")
{:ok, "abc123..."}

list_sessions(username \\ :all)

Lists active sessions.

Parameters

  • username - Username (:all for all sessions)

Returns

List of maps with session information:

[
  %{
    token_prefix: "abc123...",
    username: "admin",
    ip: "192.168.1.1",
    created_at: 1234567890,
    expires_at: 1234571490,
    last_activity: 1234570000,
    ip_binding_disabled: false
  },
  ...
]

revoke_all_sessions(username)

Revokes all sessions for a user.

Parameters

  • username - Username

Returns

  • {:ok, count} - Number of sessions revoked

revoke_session(token)

Revokes a specific session.

Parameters

  • token - Token of the session to revoke

Returns

  • :ok - Session successfully revoked
  • {:error, :session_not_found} - Token does not exist

validate_session(token, client_ip, user_agent \\ "")

Validates an existing session.

Checks:

  • Token exists
  • Session has not expired
  • IP binding (if enabled and not behind a trusted proxy)
  • User-Agent binding (if session_ua_binding is enabled; applies even behind a trusted proxy)

Parameters

  • token - Session token
  • client_ip - Current client IP
  • user_agent - Current request User-Agent (compared when session_ua_binding is on)

Returns

  • {:ok, session_data} - Valid session, returns data
  • {:error, :session_expired} - Session expired
  • {:error, :session_hijack_attempt} - IP or UA do not match
  • {:error, :invalid_session} - Token does not exist

Audit effects

Expiry is reported ahead of a binding mismatch, because expiry is the accurate reason and a legitimate client whose IP moved (NAT, mobile) should not be told it looks like an attacker. The mismatch is still recorded, so one call can emit two audit events: a token that is both expired and presented from a new IP logs :session_expired and :session_hijack_attempt together, and returns :session_expired.

Two consequences for anyone reading Malachi.Metrics, which exposes these as separate counters:

  • The audit counters are not disjoint. Summing them does not give a count of failed validations.
  • :session_hijack_attempt counts a token presented with a binding that does not match (an unexpected IP, or a different User-Agent when session_ua_binding is on), not a live session stolen. The event metadata's mismatch field lists which dimension(s) differed. Ordinary timeouts behind a changing NAT reach it, so alert thresholds should be set against that broader meaning.