diff --git a/alembic/versions/e8f2c4b6d9a1_drop_node_public_key.py b/alembic/versions/e8f2c4b6d9a1_drop_node_public_key.py new file mode 100644 index 0000000..dd40403 --- /dev/null +++ b/alembic/versions/e8f2c4b6d9a1_drop_node_public_key.py @@ -0,0 +1,43 @@ +"""Drop node_public_key table + +Revision ID: e8f2c4b6d9a1 +Revises: c6b1d8f2a9e3 +Create Date: 2026-05-25 00:00:00.000000 + +""" + +from collections.abc import Sequence + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "e8f2c4b6d9a1" +down_revision: str | None = "c6b1d8f2a9e3" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + op.drop_index("idx_node_public_key_public_key", table_name="node_public_key") + op.drop_index("idx_node_public_key_node_id", table_name="node_public_key") + op.drop_table("node_public_key") + + +def downgrade() -> None: + op.create_table( + "node_public_key", + sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True), + sa.Column("node_id", sa.BigInteger(), nullable=False), + sa.Column("public_key", sa.String(), nullable=False), + sa.Column("first_seen_us", sa.BigInteger(), nullable=True), + sa.Column("last_seen_us", sa.BigInteger(), nullable=True), + ) + op.create_index("idx_node_public_key_node_id", "node_public_key", ["node_id"], unique=False) + op.create_index( + "idx_node_public_key_public_key", + "node_public_key", + ["public_key"], + unique=False, + ) diff --git a/meshtastic/protobuf/UPSTREAM_REV.txt b/meshtastic/protobuf/UPSTREAM_REV.txt index 3a38980..ad080a8 100644 --- a/meshtastic/protobuf/UPSTREAM_REV.txt +++ b/meshtastic/protobuf/UPSTREAM_REV.txt @@ -1 +1 @@ -cb1f89372a70b0d4b4f8caf05aec28de8d4a13e0 +59cb394dcfc4432cb216358ca26e861c7d13f462 diff --git a/meshview/lang/en.json b/meshview/lang/en.json index 1b2691a..3b6b9d9 100644 --- a/meshview/lang/en.json +++ b/meshview/lang/en.json @@ -243,7 +243,6 @@ "share_contact_qr": "Share Contact QR", "copy_url": "Copy URL", "copied": "Copied!", - "potential_impersonation": "Potential Impersonation Detected", "scan_qr_to_add": "Scan this QR code to add this node as a contact on another device." }, "packet": { diff --git a/meshview/lang/es.json b/meshview/lang/es.json index e3d6eb3..7d21ec1 100644 --- a/meshview/lang/es.json +++ b/meshview/lang/es.json @@ -229,7 +229,6 @@ "share_contact_qr": "Compartir contacto QR", "copy_url": "Copiar URL", "copied": "¡Copiado!", - "potential_impersonation": "Posible suplantación detectada", "scan_qr_to_add": "Escanea este código QR para agregar este nodo como contacto en otro dispositivo." }, diff --git a/meshview/lang/ru.json b/meshview/lang/ru.json index f914d59..f1727ba 100644 --- a/meshview/lang/ru.json +++ b/meshview/lang/ru.json @@ -224,7 +224,6 @@ "share_contact_qr": "Поделиться QR контакта", "copy_url": "Копировать URL", "copied": "Скопировано!", - "potential_impersonation": "Обнаружено возможное самозванство", "scan_qr_to_add": "Отсканируйте этот QR-код, чтобы добавить эту ноду в качестве контакта на другом устройстве." }, "packet": { diff --git a/meshview/models.py b/meshview/models.py index b450336..70fbc30 100644 --- a/meshview/models.py +++ b/meshview/models.py @@ -108,21 +108,6 @@ class Traceroute(Base): ) -class NodePublicKey(Base): - __tablename__ = "node_public_key" - - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - node_id: Mapped[int] = mapped_column(BigInteger, nullable=False) - public_key: Mapped[str] = mapped_column(nullable=False) - first_seen_us: Mapped[int] = mapped_column(BigInteger, nullable=True) - last_seen_us: Mapped[int] = mapped_column(BigInteger, nullable=True) - - __table_args__ = ( - Index("idx_node_public_key_node_id", "node_id"), - Index("idx_node_public_key_public_key", "public_key"), - ) - - class DailySnapshot(Base): __tablename__ = "daily_snapshot" diff --git a/meshview/mqtt_store.py b/meshview/mqtt_store.py index 82ddd0a..9e25404 100644 --- a/meshview/mqtt_store.py +++ b/meshview/mqtt_store.py @@ -12,7 +12,7 @@ from meshtastic.protobuf.config_pb2 import Config from meshtastic.protobuf.mesh_pb2 import HardwareModel from meshtastic.protobuf.portnums_pb2 import PortNum from meshview import decode_payload, mqtt_database -from meshview.models import DailySnapshot, Node, NodePublicKey, Packet, PacketSeen, Traceroute +from meshview.models import DailySnapshot, Node, Packet, PacketSeen, Traceroute logger = logging.getLogger(__name__) @@ -292,27 +292,6 @@ async def process_envelope(topic, env): ) session.add(node) - if user.public_key: - public_key_hex = user.public_key.hex() - existing_key = ( - await session.execute( - select(NodePublicKey).where( - NodePublicKey.node_id == node_id, - NodePublicKey.public_key == public_key_hex, - ) - ) - ).scalar_one_or_none() - - if existing_key: - existing_key.last_seen_us = now_us - else: - new_key = NodePublicKey( - node_id=node_id, - public_key=public_key_hex, - first_seen_us=now_us, - last_seen_us=now_us, - ) - session.add(new_key) except Exception as e: print(f"Error processing NODEINFO_APP: {e}") diff --git a/meshview/templates/node.html b/meshview/templates/node.html index d2150e7..1287179 100644 --- a/meshview/templates/node.html +++ b/meshview/templates/node.html @@ -296,32 +296,6 @@ color:#fff; } -/* --- Impersonation Warning --- */ -.impersonation-warning { - background: rgba(239, 68, 68, 0.15); - border: 1px solid rgba(239, 68, 68, 0.4); - border-radius: 8px; - padding: 12px 16px; - margin-bottom: 14px; - display: flex; - align-items: flex-start; - gap: 10px; -} -.impersonation-warning .warning-icon { - font-size: 1.2rem; -} -.impersonation-warning .warning-content { - flex: 1; -} -.impersonation-warning .warning-title { - color: #f87171; - font-weight: bold; - margin-bottom: 4px; -} -.impersonation-warning .warning-text { - font-size: 0.85rem; - color: #ccc; -} {% endblock %} {% block body %} @@ -351,15 +325,6 @@ - - -
@@ -1637,8 +1602,7 @@ document.addEventListener("DOMContentLoaded", async () => { requestAnimationFrame(async () => { await loadNodeInfo(); - // Load QR code URL and impersonation check - await loadNodeQrAndImpersonation(); + await loadNodeQr(); // ✅ MAP MUST EXIST FIRST if (!map) initMap(); @@ -1774,16 +1738,11 @@ async function loadNodeStats(nodeId) { let currentMeshtasticUrl = ""; -async function loadNodeQrAndImpersonation() { +async function loadNodeQr() { const actionsDiv = document.getElementById("nodeActions"); - const warningDiv = document.getElementById("impersonationWarning"); try { - const [qrRes, impRes] = await Promise.all([ - fetch(`/api/node/${fromNodeId}/qr`), - fetch(`/api/node/${fromNodeId}/impersonation-check`) - ]); - + const qrRes = await fetch(`/api/node/${fromNodeId}/qr`); const qrData = await qrRes.json(); if (qrRes.ok && qrData.meshtastic_url) { currentMeshtasticUrl = qrData.meshtastic_url; @@ -1791,19 +1750,9 @@ async function loadNodeQrAndImpersonation() { } else { actionsDiv.style.display = "none"; } - - const impData = await impRes.json(); - if (impRes.ok && impData.potential_impersonation) { - warningDiv.style.display = "flex"; - document.getElementById("impersonationText").textContent = - impData.warning || `This node has sent ${impData.unique_public_key_count} different public keys. This could indicate impersonation.`; - } else { - warningDiv.style.display = "none"; - } } catch (err) { - console.error("Failed to load QR/impersonation data:", err); + console.error("Failed to load QR data:", err); actionsDiv.style.display = "none"; - warningDiv.style.display = "none"; } } diff --git a/meshview/web_api/api.py b/meshview/web_api/api.py index 9c6738c..1a8956d 100644 --- a/meshview/web_api/api.py +++ b/meshview/web_api/api.py @@ -13,7 +13,7 @@ from meshtastic.protobuf.portnums_pb2 import PortNum from meshview import database, decode_payload, store from meshview.__version__ import __version__, _git_revision_short, get_version_info from meshview.config import CONFIG -from meshview.models import DailySnapshot, Node, NodePublicKey +from meshview.models import DailySnapshot, Node from meshview.models import Packet as PacketModel from meshview.models import PacketSeen as PacketSeenModel from meshview.radio.coverage import ( @@ -1097,44 +1097,6 @@ async def api_node_qr(request): return web.json_response({"error": f"Failed to generate URL: {str(e)}"}, status=500) -@routes.get("/api/node/{node_id}/impersonation-check") -async def api_node_impersonation_check(request): - """ - Check if a node has multiple different public keys, which could indicate impersonation. - """ - try: - node_id_str = request.match_info["node_id"] - node_id = int(node_id_str, 0) - except (KeyError, ValueError): - return web.json_response({"error": "Invalid node_id"}, status=400) - - try: - async with database.async_session() as session: - result = await session.execute( - select(NodePublicKey.public_key).where(NodePublicKey.node_id == node_id).distinct() - ) - public_keys = result.scalars().all() - - unique_key_count = len(public_keys) - - return web.json_response( - { - "node_id": node_id, - "unique_public_key_count": unique_key_count, - "potential_impersonation": unique_key_count > 1, - "public_keys": public_keys - if unique_key_count <= 3 - else public_keys[:3] + ["..."], - "warning": "Multiple different public keys detected. This node may be getting impersonated." - if unique_key_count > 1 - else None, - } - ) - except Exception as e: - logger.error(f"Error checking impersonation for node {node_id}: {e}") - return web.json_response({"error": "Failed to check impersonation"}, status=500) - - @routes.get("/api/coverage/{node_id}") async def api_coverage(request): try: diff --git a/startdb.py b/startdb.py index eeea280..1ef461d 100644 --- a/startdb.py +++ b/startdb.py @@ -213,16 +213,6 @@ async def daily_cleanup_at( ) cleanup_logger.info(f"Deleted {result.rowcount} rows from Traceroute") - # ------------------------- - # NodePublicKey - # ------------------------- - result = await session.execute( - delete(models.NodePublicKey).where( - models.NodePublicKey.last_seen_us < cutoff_us - ) - ) - cleanup_logger.info(f"Deleted {result.rowcount} rows from NodePublicKey") - # ------------------------- # Node # -------------------------