From 344636416c080234ae9823aa59fc23b0bd623f24 Mon Sep 17 00:00:00 2001 From: Drew McCalmont Date: Mon, 22 Jun 2026 23:17:38 -0400 Subject: [PATCH] feat(sensor): grant read-only telemetry via allow_read_only/guest password SensorMesh::handleLoginReq only accepted the admin password or an existing ACL entry, so the guest_password and allow_read_only prefs were inert for sensor nodes. Port the login cascade already used by the repeater and room-server examples: a matching guest password, or the open allow_read_only switch, now grants PERM_ACL_READ_ONLY. READ_ONLY is sufficient to read current telemetry and the min/avg/max history while admin access stays password-gated. Transient read-only viewers are not persisted to flash to avoid per-viewer FS writes. Co-Authored-By: Claude Opus 4.8 (1M context) --- examples/simple_sensor/SensorMesh.cpp | 36 ++++++++++++++++++--------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 879fcbf026..f2870d453f 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -328,19 +328,29 @@ int SensorMesh::getAGCResetInterval() const { } uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood) { - ClientInfo* client; + ClientInfo* client = NULL; if (data[0] == 0) { // blank password, just check if sender is in ACL client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); - if (client == NULL) { - #if MESH_DEBUG - MESH_DEBUG_PRINTLN("Login, sender not in ACL"); - #endif - return 0; - } - } else { - if (strcmp((char *) data, _prefs.password) != 0) { // check for valid admin password + #if MESH_DEBUG + if (client == NULL) MESH_DEBUG_PRINTLN("Login, sender not in ACL"); + #endif + } + if (client == NULL) { + // Determine the role this login grants. Admin needs the admin + // password; a matching guest password (if set) or the open + // allow_read_only switch grants READ_ONLY, which is enough to read + // telemetry (current + min/avg/max history). Mirrors the login + // cascade used by the repeater and room-server examples. + uint8_t perms; + if (strcmp((char *) data, _prefs.password) == 0) { // valid admin password + perms = PERM_ACL_ADMIN; + } else if (_prefs.guest_password[0] != 0 && strcmp((char *) data, _prefs.guest_password) == 0) { + perms = PERM_ACL_READ_ONLY; // matched the configured guest password + } else if (_prefs.allow_read_only) { + perms = PERM_ACL_READ_ONLY; // open read-only telemetry for everyone + } else { #if MESH_DEBUG - MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]); + MESH_DEBUG_PRINTLN("Invalid password"); #endif return 0; } @@ -354,10 +364,12 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* MESH_DEBUG_PRINTLN("Login success!"); client->last_timestamp = sender_timestamp; client->last_activity = getRTCClock()->getCurrentTime(); - client->permissions |= PERM_ACL_ADMIN; + client->permissions = (client->permissions & ~PERM_ACL_ROLE_MASK) | perms; memcpy(client->shared_secret, secret, PUB_KEY_SIZE); - dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); + if (perms != PERM_ACL_READ_ONLY) { // avoid an FS write per transient read-only viewer + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); + } } if (is_flood) {