made changes

This commit is contained in:
pablorevilla-meshtastic
2026-05-25 09:07:59 -07:00
parent 2ecaaae2a9
commit db04f4dc9f
10 changed files with 50 additions and 145 deletions
@@ -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,
)
+1 -1
View File
@@ -1 +1 @@
cb1f89372a70b0d4b4f8caf05aec28de8d4a13e0
59cb394dcfc4432cb216358ca26e861c7d13f462
-1
View File
@@ -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": {
-1
View File
@@ -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."
},
-1
View File
@@ -224,7 +224,6 @@
"share_contact_qr": "Поделиться QR контакта",
"copy_url": "Копировать URL",
"copied": "Скопировано!",
"potential_impersonation": "Обнаружено возможное самозванство",
"scan_qr_to_add": "Отсканируйте этот QR-код, чтобы добавить эту ноду в качестве контакта на другом устройстве."
},
"packet": {
-15
View File
@@ -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"
+1 -22
View File
@@ -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}")
+4 -55
View File
@@ -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 @@
</a>
</div>
<!-- Impersonation Warning -->
<div id="impersonationWarning" class="impersonation-warning" style="display:none;">
<span class="warning-icon">⚠️</span>
<div class="warning-content">
<div class="warning-title" data-translate-lang="potential_impersonation">Potential Impersonation Detected</div>
<div class="warning-text" id="impersonationText"></div>
</div>
</div>
<!-- Node Info -->
<div id="node-info" class="node-info">
<div class="node-info-col">
@@ -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";
}
}
+1 -39
View File
@@ -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:
-10
View File
@@ -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
# -------------------------