Skip to content

Authentication Module

Reference documentation for main/modules/authentication/. Covers data structures, the full ESP3DAuthenticationService API, session mechanics, NVS settings, and integration points. For the service lifecycle, orchestration by ESP3DNetworkServices, and per-service defensive purge, see authentication_lifecycle.md.


# CMakeLists.txt
option(ESP3D_AUTHENTICATION_FEATURE ...)
ESP3D_AUTHENTICATION_FEATUREEffect
ONFull session-based authentication is active. Passwords and timeout are loaded from NVS. All session management APIs compile in.
OFFEvery auth check unconditionally returns ESP3DAuthenticationLevel::admin. No sessions, no mutex, no NVS reads. Zero runtime overhead.

The flag is checked by #if ESP3D_AUTHENTICATION_FEATURE throughout the module and in every command handler (esp0.cpp, esp550.cpp, esp555.cpp, esp510.cpp, …).


Defined in main/modules/authentication/esp3d_authentication_types.h:

enum class ESP3DAuthenticationLevel : uint8_t {
guest, // unauthenticated / unknown
user, // logged in as the user account
admin, // logged in as the admin account
not_authenticated,
};

The ordering guest < user < admin is used implicitly by command handlers — a command requiring admin rejects any level below it.

When ESP3D_AUTHENTICATION_FEATURE is OFF, getAuthenticatedLevel() always returns admin regardless of any password argument, so all commands are accessible without credentials.


Defined in main/modules/authentication/esp3d_authentication_records.h. Active only when ESP3D_AUTHENTICATION_FEATURE is ON.

struct ESP3DAuthenticationRecord {
ESP3DAuthenticationLevel level; // granted level for this session
int socket_id; // underlying socket fd (-1 = any)
ESP3DClientType client_type; // which service owns this session
char session_id[25]; // 24-char hex string + NUL
int64_t last_time; // esp3d_hal::millis() at record creation
};

client_type discriminates sessions from different transports that share the same list. Current values used:

ESP3DClientTypeService that creates the record
webuiHTTP login handler (esp3d_login.cpp), cookie ESP3D_SESSIONID
webui_websocketWebSocket /ws endpoint (esp3dWsWebUiService)
websocket_serverWebSocket /wsdata endpoint (esp3dWsServerDataService)
socket_serverTelnet / raw socket server (esp3d_socket_server.cpp)

last_time is set to esp3d_hal::millis() at record creation and refreshed on activity, so both timeouts are inactivity timeouts: the HTTP service refreshes it on each authenticated request (and the WebUI PING on /ws), stream clients on every received line or frame through touchRecord().

Memory cost: ~50 bytes per record (fixed-size struct, no heap allocation per field). At MAX_SESSION_RECORDS = 10 the session list consumes at most ~500 bytes of heap including std::list node overhead — acceptable even in the worst-case Bluetooth configuration (~10 KB free heap).


Singleton declared extern in esp3d_authentication.h:

extern ESP3DAuthenticationService esp3dAuthenthicationService;

The instance is defined in esp3d_authentication.cpp. Do not create additional instances — the mutex, session list, and passwords are instance-local.

MethodWhat it does
begin()Reads _admin_pwd, _user_pwd, _session_timeout and _stream_session_timeout (a14) from NVS. Does not touch _sessions.
handle()Calls purgeExpiredSessions(). Called every loop tick from ESP3DNetworkServices::handle().
end()Clears passwords and calls clearAllSessions().

Only ESP3DNetworkServices calls these — HTTP, WebSocket, and socket server do not call them directly. See authentication_lifecycle.md for the full orchestration sequence.

ESP3DAuthenticationLevel getAuthenticatedLevel(const char *pwd = nullptr);
bool isAdmin(const char *pwd);
bool isUser(const char *pwd);

getAuthenticatedLevel() checks admin first, then user, and returns guest if neither matches. Both isAdmin() and isUser() return false on a nullptr argument. With the feature OFF they always return true / admin.

Passwords are compared with std::string::operator== — exact, case-sensitive, binary match against the NVS-stored value.

const char *create_session_id(struct sockaddr_storage source_addr,
int socketId);

Generates a 24-character uppercase hex string from 12 bytes of the hardware RNG (esp_fill_random(), 96 random bits). Since a14 nothing predictable goes in: no millis(), IP or socket (the previous ID mixed 8 random bytes with 4 bytes of millis()). The length stays 24, as checked by the cookie parser, clearSession() and the /ws handshake. esp_random() is a true RNG while WiFi or BT is on, which is always the case when a network session can be created.

The result is written into a static char[25] buffer — not thread-safe and not re-entrant. Callers must copy the returned pointer before any other call. The source_addr and socketId parameters are accepted for API compatibility but are not used.

All methods are no-ops or return safe defaults when ESP3D_AUTHENTICATION_FEATURE is OFF.

bool createRecord(const char *sessionId, int socketId,
ESP3DAuthenticationLevel level,
ESP3DClientType client_type);

Returns false if sessionId is null, empty, or longer than 24 chars. Acquires _sessions_mutex, then:

  1. Calls purgeExpiredSessions_nolock() to shrink the list before insertion.
  2. If _sessions.size() >= MAX_SESSION_RECORDS (10), evicts the record with the smallest last_time across all client types.
  3. Pushes the new record to the back of _sessions.
ESP3DAuthenticationRecord *getRecord(const char *sessionId);
ESP3DAuthenticationRecord *getRecord(int socketId,
ESP3DClientType client_type);

Both return a raw pointer into the list, or NULL on miss. The pointer is valid only while no concurrent clearSession() or clearAllSessions() runs. Use it immediately within the same request handler scope. getRecord(-1, client_type) matches any socket when socketId == -1.

bool updateRecord(int socketId, ESP3DClientType client_type,
ESP3DAuthenticationLevel newlevel);

Updates the level field of the first matching record. Returns false if no match is found.

bool clearSession(const char *sessionId);
bool clearSession(int socketId, ESP3DClientType client_type);

clearSession(sessionId) requires exactly a 24-character string; returns false otherwise. clearSession(socketId, client_type) removes all records matching the pair and returns true if at least one was removed.

void clearSessions(ESP3DClientType client_type);
void clearAllSessions();

clearSessions(type) is called by each service from its own end() to purge its bucket. clearAllSessions() is called by ESP3DNetworkServices::end().

bool touchRecord(const char *sessionId, ESP3DAuthenticationLevel &level,
int64_t *idle_ms = nullptr);
bool isValidSession(const char *sessionId, ESP3DClientType client_type);
uint64_t timeoutFor(ESP3DClientType client_type);
  • touchRecord(): lookup, expiry check and refresh under one lock, so no record pointer leaves the service. Returns true and the level when the session is valid (its inactivity timer restarts; idle_ms gives the idle time before this refresh, used for the WebUI PING answer). An expired record not yet purged by handle() is cleared and false is returned. Used by the WebSocket and telnet services on every line / frame.
  • isValidSession(): read-only check for a session of this client type; does not restart the timer (used by the periodic /ws checks: neither outgoing data nor a check may keep a session alive).
  • timeoutFor(): the stream timeout for socket_server (telnet) and websocket_server (/wsdata), which have no keepalive such as the WebUI PING; the HTTP timeout for the other client types.

See esp3d_x_stream_authentication.md for the per-line model.

uint8_t activeSessionsCount(ESP3DClientType type);

Returns the number of records for the given client type. Does not distinguish expired-but-not-yet-purged records from active ones.

Two independent policies bound the session list:

PolicyTriggerRule
Time-based expiryPeriodic via handle() → purgeExpiredSessions()Remove every record where millis() − last_time ≥ timeoutFor(client_type). A timeout of 0 disables expiry for that kind of session only.
Capacity evictionOn createRecord() when size >= 10Evict the record with the smallest last_time regardless of client type.

The timeout getter converts stored minutes to milliseconds:

uint64_t getSessionTimeout() { return 60 * 1000 * _session_timeout; } // HTTP / WebUI
uint64_t getStreamSessionTimeout() { return 60 * 1000 * _stream_session_timeout; } // telnet, /wsdata

Both are set with [ESP510] (HTTP= / STREAM=, minutes, 0–255). Since a14 the purge no longer returns early when the HTTP timeout is 0: each record is checked against its own timeout.

_sessions_mutex (a POSIX pthread_mutex_t) serializes every read and write on _sessions.

  • Acquire, operate, release — no external service is called while the mutex is held. esp3d_log calls inside the lock are acceptable.
  • purgeExpiredSessions_nolock() is the non-locking variant, called only from within createRecord() which already holds the mutex.
  • getRecord() returns a raw pointer after releasing the mutex. This is a known accepted TOCTOU residual: callers use the pointer immediately within the same request handler, where no concurrent clear is expected. The stream paths use touchRecord() / isValidSession() instead (no pointer leaves the service).

Active only when ESP3D_AUTHENTICATION_FEATURE is ON. Defined in main/core/includes/esp3d_settings_defs.inc:

Setting indexTypeDefaultDescription
esp3d_admin_passwordstring"admin"Admin-level password
esp3d_user_passwordstring"user"User-level password
esp3d_session_timeoutuint80HTTP session inactivity timeout in minutes; 0 = never expire
esp3d_stream_session_timeout (a14)uint80Stream (telnet, /wsdata) inactivity timeout in minutes, NVS key auth_stream_to, INI [services] Stream_session_timeout; 0 = never expire

ESP commands that read/write these settings:

CommandSetting touched
ESP550Admin password (read/write)
ESP555User password (read/write)
ESP510Session timeout (read/write)
ESP400 / ESP401Bulk settings dump / write (includes all three)

begin() must be called after any password or timeout change to reload the in-memory values from NVS. Today a full network restart (ESP3DNetworkServices::end() + begin()) is the only code path that triggers this.


  • Creates sessions via createRecord() on successful login; session ID is sent as the ESP3D_SESSIONID cookie.
  • Looks up sessions via getRecord(sessionId) on every authenticated request.
  • Calls clearSession(sessionId) on explicit logout.
  • Calls clearSessions(ESP3DClientType::webui) from its own end().

HTTP sessions are cookie-based and outlive the underlying TCP socket, which is why the bulk clear in ESP3DHttpService::end() is necessary in addition to any per-socket teardown.

Two instances share ESP3DWsService, distinguished by authClientType(): webui_websocket for /ws, websocket_server for /wsdata. pushMsgToRxQueue() calls createRecord() and tags the resulting message’s origin with the same ESP3DClientType, ensuring command replies are routed back to the correct endpoint rather than the wrong broadcast pool. Each instance calls clearSessions(authClientType()) from its own end().

See authentication_lifecycle.md §“Client types sharing the session list” and websockets_protocol.md for the full routing story.

Socket server / Telnet (ESP3DSocketServer)

Section titled “Socket server / Telnet (ESP3DSocketServer)”
  • Creates sessions via createRecord() on connection (ESP3DClientType::socket_server).
  • Looks up sessions via getRecord(socketId, socket_server).
  • Calls clearSession(socketId, socket_server) on disconnect.
  • Calls clearSessions(ESP3DClientType::socket_server) from its own end().

onNetworkAuthClick in main/display/cnc/screens/settings_list_screen.cpp launches the PIN input screen for changing the admin password. After saving, the value is written to NVS via ESP550; a full network restart applies it.