mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-11 11:13:04 +02:00
Polish off all our gross edges around frontend/backend Public name management
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
PUBLIC_CHANNEL_KEY = "8B3387E9C5CDEA6AC9E5EDBAA115CD72"
|
||||
PUBLIC_CHANNEL_NAME = "Public"
|
||||
|
||||
|
||||
def is_public_channel_key(key: str) -> bool:
|
||||
return key.upper() == PUBLIC_CHANNEL_KEY
|
||||
|
||||
|
||||
def is_public_channel_name(name: str) -> bool:
|
||||
return name.casefold() == PUBLIC_CHANNEL_NAME.casefold()
|
||||
+5
-7
@@ -17,6 +17,7 @@ from contextlib import asynccontextmanager
|
||||
|
||||
from meshcore import EventType, MeshCore
|
||||
|
||||
from app.channel_constants import PUBLIC_CHANNEL_KEY, PUBLIC_CHANNEL_NAME
|
||||
from app.config import settings
|
||||
from app.event_handlers import cleanup_expired_acks
|
||||
from app.models import Contact, ContactUpsert
|
||||
@@ -443,16 +444,13 @@ async def ensure_default_channels() -> None:
|
||||
This seeds the canonical Public channel row in the database if it is missing
|
||||
or misnamed. It does not make the channel undeletable through the router.
|
||||
"""
|
||||
# Public channel - no hashtag, specific well-known key
|
||||
PUBLIC_CHANNEL_KEY_HEX = "8B3387E9C5CDEA6AC9E5EDBAA115CD72"
|
||||
|
||||
# Check by KEY (not name) since that's what's fixed
|
||||
existing = await ChannelRepository.get_by_key(PUBLIC_CHANNEL_KEY_HEX)
|
||||
if not existing or existing.name != "Public":
|
||||
existing = await ChannelRepository.get_by_key(PUBLIC_CHANNEL_KEY)
|
||||
if not existing or existing.name != PUBLIC_CHANNEL_NAME:
|
||||
logger.info("Ensuring default Public channel exists with correct name")
|
||||
await ChannelRepository.upsert(
|
||||
key=PUBLIC_CHANNEL_KEY_HEX,
|
||||
name="Public",
|
||||
key=PUBLIC_CHANNEL_KEY,
|
||||
name=PUBLIC_CHANNEL_NAME,
|
||||
is_hashtag=False,
|
||||
on_radio=existing.on_radio if existing else False,
|
||||
)
|
||||
|
||||
+47
-7
@@ -4,6 +4,12 @@ from hashlib import sha256
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.channel_constants import (
|
||||
PUBLIC_CHANNEL_KEY,
|
||||
PUBLIC_CHANNEL_NAME,
|
||||
is_public_channel_key,
|
||||
is_public_channel_name,
|
||||
)
|
||||
from app.models import Channel, ChannelDetail, ChannelMessageCounts, ChannelTopSender
|
||||
from app.region_scope import normalize_region_scope
|
||||
from app.repository import ChannelRepository, MessageRepository
|
||||
@@ -62,10 +68,31 @@ async def create_channel(request: CreateChannelRequest) -> Channel:
|
||||
Channels are NOT pushed to radio on creation. They are loaded to the radio
|
||||
automatically when sending a message (see messages.py send_channel_message).
|
||||
"""
|
||||
is_hashtag = request.name.startswith("#")
|
||||
requested_name = request.name
|
||||
is_hashtag = requested_name.startswith("#")
|
||||
|
||||
# Determine the channel secret
|
||||
if request.key and not is_hashtag:
|
||||
# Reserve the canonical Public room so it cannot drift to another key,
|
||||
# and the well-known Public key cannot be renamed to something else.
|
||||
if is_public_channel_name(requested_name):
|
||||
if request.key:
|
||||
try:
|
||||
key_bytes = bytes.fromhex(request.key)
|
||||
if len(key_bytes) != 16:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Channel key must be exactly 16 bytes (32 hex chars)",
|
||||
)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid hex string for key") from None
|
||||
if key_bytes.hex().upper() != PUBLIC_CHANNEL_KEY:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f'"{PUBLIC_CHANNEL_NAME}" must use the canonical Public key',
|
||||
)
|
||||
key_hex = PUBLIC_CHANNEL_KEY
|
||||
channel_name = PUBLIC_CHANNEL_NAME
|
||||
is_hashtag = False
|
||||
elif request.key and not is_hashtag:
|
||||
try:
|
||||
key_bytes = bytes.fromhex(request.key)
|
||||
if len(key_bytes) != 16:
|
||||
@@ -74,17 +101,25 @@ async def create_channel(request: CreateChannelRequest) -> Channel:
|
||||
)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid hex string for key") from None
|
||||
key_hex = key_bytes.hex().upper()
|
||||
if is_public_channel_key(key_hex):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f'The canonical Public key may only be used for "{PUBLIC_CHANNEL_NAME}"',
|
||||
)
|
||||
channel_name = requested_name
|
||||
else:
|
||||
# Derive key from name hash (same as meshcore library does)
|
||||
key_bytes = sha256(request.name.encode("utf-8")).digest()[:16]
|
||||
key_bytes = sha256(requested_name.encode("utf-8")).digest()[:16]
|
||||
key_hex = key_bytes.hex().upper()
|
||||
channel_name = requested_name
|
||||
|
||||
key_hex = key_bytes.hex().upper()
|
||||
logger.info("Creating channel %s: %s (hashtag=%s)", key_hex, request.name, is_hashtag)
|
||||
logger.info("Creating channel %s: %s (hashtag=%s)", key_hex, channel_name, is_hashtag)
|
||||
|
||||
# Store in database only - radio sync happens at send time
|
||||
await ChannelRepository.upsert(
|
||||
key=key_hex,
|
||||
name=request.name,
|
||||
name=channel_name,
|
||||
is_hashtag=is_hashtag,
|
||||
on_radio=False,
|
||||
)
|
||||
@@ -140,6 +175,11 @@ async def delete_channel(key: str) -> dict:
|
||||
Note: This does not clear the channel from the radio. The radio's channel
|
||||
slots are managed separately (channels are loaded temporarily when sending).
|
||||
"""
|
||||
if is_public_channel_key(key):
|
||||
raise HTTPException(
|
||||
status_code=400, detail="The canonical Public channel cannot be deleted"
|
||||
)
|
||||
|
||||
logger.info("Deleting channel %s from database", key)
|
||||
await ChannelRepository.delete(key)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user