diff --git a/app/channel_constants.py b/app/channel_constants.py new file mode 100644 index 00000000..af496f89 --- /dev/null +++ b/app/channel_constants.py @@ -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() diff --git a/app/radio_sync.py b/app/radio_sync.py index 82cec7a0..30cede6a 100644 --- a/app/radio_sync.py +++ b/app/radio_sync.py @@ -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, ) diff --git a/app/routers/channels.py b/app/routers/channels.py index 0537bfbf..ca4de076 100644 --- a/app/routers/channels.py +++ b/app/routers/channels.py @@ -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) diff --git a/frontend/prebuilt/assets/BotCodeEditor-BzmXJa-U.js b/frontend/prebuilt/assets/BotCodeEditor-DSkSGQHd.js similarity index 99% rename from frontend/prebuilt/assets/BotCodeEditor-BzmXJa-U.js rename to frontend/prebuilt/assets/BotCodeEditor-DSkSGQHd.js index 82b29951..ff3037b6 100644 Binary files a/frontend/prebuilt/assets/BotCodeEditor-BzmXJa-U.js and b/frontend/prebuilt/assets/BotCodeEditor-DSkSGQHd.js differ diff --git a/frontend/prebuilt/assets/BotCodeEditor-BzmXJa-U.js.map b/frontend/prebuilt/assets/BotCodeEditor-DSkSGQHd.js.map similarity index 99% rename from frontend/prebuilt/assets/BotCodeEditor-BzmXJa-U.js.map rename to frontend/prebuilt/assets/BotCodeEditor-DSkSGQHd.js.map index e7769f85..246e6fb1 100644 Binary files a/frontend/prebuilt/assets/BotCodeEditor-BzmXJa-U.js.map and b/frontend/prebuilt/assets/BotCodeEditor-DSkSGQHd.js.map differ diff --git a/frontend/prebuilt/assets/CrackerPanel-C3rwFxw9.js b/frontend/prebuilt/assets/CrackerPanel-o4UFAt5V.js similarity index 99% rename from frontend/prebuilt/assets/CrackerPanel-C3rwFxw9.js rename to frontend/prebuilt/assets/CrackerPanel-o4UFAt5V.js index 8683859a..64ae6fb9 100644 Binary files a/frontend/prebuilt/assets/CrackerPanel-C3rwFxw9.js and b/frontend/prebuilt/assets/CrackerPanel-o4UFAt5V.js differ diff --git a/frontend/prebuilt/assets/CrackerPanel-C3rwFxw9.js.map b/frontend/prebuilt/assets/CrackerPanel-o4UFAt5V.js.map similarity index 99% rename from frontend/prebuilt/assets/CrackerPanel-C3rwFxw9.js.map rename to frontend/prebuilt/assets/CrackerPanel-o4UFAt5V.js.map index 5809b54e..3855a586 100644 Binary files a/frontend/prebuilt/assets/CrackerPanel-C3rwFxw9.js.map and b/frontend/prebuilt/assets/CrackerPanel-o4UFAt5V.js.map differ diff --git a/frontend/prebuilt/assets/MapView-BW3wM0G8.js b/frontend/prebuilt/assets/MapView-BscCnodc.js similarity index 92% rename from frontend/prebuilt/assets/MapView-BW3wM0G8.js rename to frontend/prebuilt/assets/MapView-BscCnodc.js index 63a189f6..7c384381 100644 Binary files a/frontend/prebuilt/assets/MapView-BW3wM0G8.js and b/frontend/prebuilt/assets/MapView-BscCnodc.js differ diff --git a/frontend/prebuilt/assets/MapView-BW3wM0G8.js.map b/frontend/prebuilt/assets/MapView-BscCnodc.js.map similarity index 99% rename from frontend/prebuilt/assets/MapView-BW3wM0G8.js.map rename to frontend/prebuilt/assets/MapView-BscCnodc.js.map index d30a1962..7e0dc1fe 100644 Binary files a/frontend/prebuilt/assets/MapView-BW3wM0G8.js.map and b/frontend/prebuilt/assets/MapView-BscCnodc.js.map differ diff --git a/frontend/prebuilt/assets/NeighborsMiniMap-BfUArOef.js b/frontend/prebuilt/assets/NeighborsMiniMap-DpDSb5Gd.js similarity index 85% rename from frontend/prebuilt/assets/NeighborsMiniMap-BfUArOef.js rename to frontend/prebuilt/assets/NeighborsMiniMap-DpDSb5Gd.js index 9e9190a6..3e52a094 100644 Binary files a/frontend/prebuilt/assets/NeighborsMiniMap-BfUArOef.js and b/frontend/prebuilt/assets/NeighborsMiniMap-DpDSb5Gd.js differ diff --git a/frontend/prebuilt/assets/NeighborsMiniMap-BfUArOef.js.map b/frontend/prebuilt/assets/NeighborsMiniMap-DpDSb5Gd.js.map similarity index 97% rename from frontend/prebuilt/assets/NeighborsMiniMap-BfUArOef.js.map rename to frontend/prebuilt/assets/NeighborsMiniMap-DpDSb5Gd.js.map index faf7323e..0576fed0 100644 Binary files a/frontend/prebuilt/assets/NeighborsMiniMap-BfUArOef.js.map and b/frontend/prebuilt/assets/NeighborsMiniMap-DpDSb5Gd.js.map differ diff --git a/frontend/prebuilt/assets/PathRouteMap-Cg3mrjz1.js b/frontend/prebuilt/assets/PathRouteMap-DaW0RYbg.js similarity index 95% rename from frontend/prebuilt/assets/PathRouteMap-Cg3mrjz1.js rename to frontend/prebuilt/assets/PathRouteMap-DaW0RYbg.js index 83429567..1e7568d6 100644 Binary files a/frontend/prebuilt/assets/PathRouteMap-Cg3mrjz1.js and b/frontend/prebuilt/assets/PathRouteMap-DaW0RYbg.js differ diff --git a/frontend/prebuilt/assets/PathRouteMap-Cg3mrjz1.js.map b/frontend/prebuilt/assets/PathRouteMap-DaW0RYbg.js.map similarity index 99% rename from frontend/prebuilt/assets/PathRouteMap-Cg3mrjz1.js.map rename to frontend/prebuilt/assets/PathRouteMap-DaW0RYbg.js.map index 5183fa66..148925b8 100644 Binary files a/frontend/prebuilt/assets/PathRouteMap-Cg3mrjz1.js.map and b/frontend/prebuilt/assets/PathRouteMap-DaW0RYbg.js.map differ diff --git a/frontend/prebuilt/assets/Popup-DkSClDwo.js b/frontend/prebuilt/assets/Popup-BdPgkVK7.js similarity index 81% rename from frontend/prebuilt/assets/Popup-DkSClDwo.js rename to frontend/prebuilt/assets/Popup-BdPgkVK7.js index 7f83aec8..258b5eb7 100644 Binary files a/frontend/prebuilt/assets/Popup-DkSClDwo.js and b/frontend/prebuilt/assets/Popup-BdPgkVK7.js differ diff --git a/frontend/prebuilt/assets/Popup-DkSClDwo.js.map b/frontend/prebuilt/assets/Popup-BdPgkVK7.js.map similarity index 97% rename from frontend/prebuilt/assets/Popup-DkSClDwo.js.map rename to frontend/prebuilt/assets/Popup-BdPgkVK7.js.map index 39a7a948..2c157ee1 100644 Binary files a/frontend/prebuilt/assets/Popup-DkSClDwo.js.map and b/frontend/prebuilt/assets/Popup-BdPgkVK7.js.map differ diff --git a/frontend/prebuilt/assets/RepeaterDashboard-DjXZo1ON.js b/frontend/prebuilt/assets/RepeaterDashboard-D9gU0mRZ.js similarity index 98% rename from frontend/prebuilt/assets/RepeaterDashboard-DjXZo1ON.js rename to frontend/prebuilt/assets/RepeaterDashboard-D9gU0mRZ.js index 2d737dbb..cbc813b6 100644 Binary files a/frontend/prebuilt/assets/RepeaterDashboard-DjXZo1ON.js and b/frontend/prebuilt/assets/RepeaterDashboard-D9gU0mRZ.js differ diff --git a/frontend/prebuilt/assets/RepeaterDashboard-DjXZo1ON.js.map b/frontend/prebuilt/assets/RepeaterDashboard-D9gU0mRZ.js.map similarity index 99% rename from frontend/prebuilt/assets/RepeaterDashboard-DjXZo1ON.js.map rename to frontend/prebuilt/assets/RepeaterDashboard-D9gU0mRZ.js.map index 1b8123a5..31616d3d 100644 Binary files a/frontend/prebuilt/assets/RepeaterDashboard-DjXZo1ON.js.map and b/frontend/prebuilt/assets/RepeaterDashboard-D9gU0mRZ.js.map differ diff --git a/frontend/prebuilt/assets/SearchView-Dvalyr5p.js b/frontend/prebuilt/assets/SearchView-DOvQv4sD.js similarity index 97% rename from frontend/prebuilt/assets/SearchView-Dvalyr5p.js rename to frontend/prebuilt/assets/SearchView-DOvQv4sD.js index 3641060f..9ea28f5b 100644 Binary files a/frontend/prebuilt/assets/SearchView-Dvalyr5p.js and b/frontend/prebuilt/assets/SearchView-DOvQv4sD.js differ diff --git a/frontend/prebuilt/assets/SearchView-Dvalyr5p.js.map b/frontend/prebuilt/assets/SearchView-DOvQv4sD.js.map similarity index 99% rename from frontend/prebuilt/assets/SearchView-Dvalyr5p.js.map rename to frontend/prebuilt/assets/SearchView-DOvQv4sD.js.map index 04848382..7ee88db0 100644 Binary files a/frontend/prebuilt/assets/SearchView-Dvalyr5p.js.map and b/frontend/prebuilt/assets/SearchView-DOvQv4sD.js.map differ diff --git a/frontend/prebuilt/assets/SettingsModal-DgO2ZLSb.js b/frontend/prebuilt/assets/SettingsModal-C0FwppK6.js similarity index 99% rename from frontend/prebuilt/assets/SettingsModal-DgO2ZLSb.js rename to frontend/prebuilt/assets/SettingsModal-C0FwppK6.js index d9f5087c..cc6eecd6 100644 Binary files a/frontend/prebuilt/assets/SettingsModal-DgO2ZLSb.js and b/frontend/prebuilt/assets/SettingsModal-C0FwppK6.js differ diff --git a/frontend/prebuilt/assets/SettingsModal-DgO2ZLSb.js.map b/frontend/prebuilt/assets/SettingsModal-C0FwppK6.js.map similarity index 99% rename from frontend/prebuilt/assets/SettingsModal-DgO2ZLSb.js.map rename to frontend/prebuilt/assets/SettingsModal-C0FwppK6.js.map index 6c0d981b..d8064d23 100644 Binary files a/frontend/prebuilt/assets/SettingsModal-DgO2ZLSb.js.map and b/frontend/prebuilt/assets/SettingsModal-C0FwppK6.js.map differ diff --git a/frontend/prebuilt/assets/VisualizerView-DlcD_Fhk.js b/frontend/prebuilt/assets/VisualizerView-C0wB47kL.js similarity index 99% rename from frontend/prebuilt/assets/VisualizerView-DlcD_Fhk.js rename to frontend/prebuilt/assets/VisualizerView-C0wB47kL.js index 1bb9a6c9..49b3a88f 100644 Binary files a/frontend/prebuilt/assets/VisualizerView-DlcD_Fhk.js and b/frontend/prebuilt/assets/VisualizerView-C0wB47kL.js differ diff --git a/frontend/prebuilt/assets/VisualizerView-DlcD_Fhk.js.map b/frontend/prebuilt/assets/VisualizerView-C0wB47kL.js.map similarity index 99% rename from frontend/prebuilt/assets/VisualizerView-DlcD_Fhk.js.map rename to frontend/prebuilt/assets/VisualizerView-C0wB47kL.js.map index 6a0f1350..2a181d05 100644 Binary files a/frontend/prebuilt/assets/VisualizerView-DlcD_Fhk.js.map and b/frontend/prebuilt/assets/VisualizerView-C0wB47kL.js.map differ diff --git a/frontend/prebuilt/assets/hooks-DS9M-GrA.js b/frontend/prebuilt/assets/hooks-DS9M-GrA.js new file mode 100644 index 00000000..135639a1 Binary files /dev/null and b/frontend/prebuilt/assets/hooks-DS9M-GrA.js differ diff --git a/frontend/prebuilt/assets/hooks-DfhmuKzA.js.map b/frontend/prebuilt/assets/hooks-DS9M-GrA.js.map similarity index 90% rename from frontend/prebuilt/assets/hooks-DfhmuKzA.js.map rename to frontend/prebuilt/assets/hooks-DS9M-GrA.js.map index 3912044c..06285355 100644 Binary files a/frontend/prebuilt/assets/hooks-DfhmuKzA.js.map and b/frontend/prebuilt/assets/hooks-DS9M-GrA.js.map differ diff --git a/frontend/prebuilt/assets/hooks-DfhmuKzA.js b/frontend/prebuilt/assets/hooks-DfhmuKzA.js deleted file mode 100644 index 1865f7e6..00000000 Binary files a/frontend/prebuilt/assets/hooks-DfhmuKzA.js and /dev/null differ diff --git a/frontend/prebuilt/assets/index-DhrMLy6_.js b/frontend/prebuilt/assets/index-D3gHLELw.js similarity index 65% rename from frontend/prebuilt/assets/index-DhrMLy6_.js rename to frontend/prebuilt/assets/index-D3gHLELw.js index d45ab6c8..1586d181 100644 Binary files a/frontend/prebuilt/assets/index-DhrMLy6_.js and b/frontend/prebuilt/assets/index-D3gHLELw.js differ diff --git a/frontend/prebuilt/assets/index-D3gHLELw.js.map b/frontend/prebuilt/assets/index-D3gHLELw.js.map new file mode 100644 index 00000000..653424e3 Binary files /dev/null and b/frontend/prebuilt/assets/index-D3gHLELw.js.map differ diff --git a/frontend/prebuilt/assets/index-DhrMLy6_.js.map b/frontend/prebuilt/assets/index-DhrMLy6_.js.map deleted file mode 100644 index 6aa688be..00000000 Binary files a/frontend/prebuilt/assets/index-DhrMLy6_.js.map and /dev/null differ diff --git a/frontend/prebuilt/assets/leaflet-lJm4twjh.js b/frontend/prebuilt/assets/leaflet-f1-64Jr-.js similarity index 99% rename from frontend/prebuilt/assets/leaflet-lJm4twjh.js rename to frontend/prebuilt/assets/leaflet-f1-64Jr-.js index 79c9787f..662966bb 100644 Binary files a/frontend/prebuilt/assets/leaflet-lJm4twjh.js and b/frontend/prebuilt/assets/leaflet-f1-64Jr-.js differ diff --git a/frontend/prebuilt/assets/leaflet-lJm4twjh.js.map b/frontend/prebuilt/assets/leaflet-f1-64Jr-.js.map similarity index 99% rename from frontend/prebuilt/assets/leaflet-lJm4twjh.js.map rename to frontend/prebuilt/assets/leaflet-f1-64Jr-.js.map index c1fc0099..3558dc59 100644 Binary files a/frontend/prebuilt/assets/leaflet-lJm4twjh.js.map and b/frontend/prebuilt/assets/leaflet-f1-64Jr-.js.map differ diff --git a/frontend/prebuilt/assets/separator-DS1eMu86.js b/frontend/prebuilt/assets/separator-CspOlRlP.js similarity index 89% rename from frontend/prebuilt/assets/separator-DS1eMu86.js rename to frontend/prebuilt/assets/separator-CspOlRlP.js index d91b37d3..9076708e 100644 Binary files a/frontend/prebuilt/assets/separator-DS1eMu86.js and b/frontend/prebuilt/assets/separator-CspOlRlP.js differ diff --git a/frontend/prebuilt/assets/separator-DS1eMu86.js.map b/frontend/prebuilt/assets/separator-CspOlRlP.js.map similarity index 98% rename from frontend/prebuilt/assets/separator-DS1eMu86.js.map rename to frontend/prebuilt/assets/separator-CspOlRlP.js.map index cdcb6a47..6b9d2bc4 100644 Binary files a/frontend/prebuilt/assets/separator-DS1eMu86.js.map and b/frontend/prebuilt/assets/separator-CspOlRlP.js.map differ diff --git a/frontend/prebuilt/index.html b/frontend/prebuilt/index.html index 2f9460f7..9caf89c9 100644 Binary files a/frontend/prebuilt/index.html and b/frontend/prebuilt/index.html differ diff --git a/frontend/src/components/ChatHeader.tsx b/frontend/src/components/ChatHeader.tsx index 0e767483..42be98d0 100644 --- a/frontend/src/components/ChatHeader.tsx +++ b/frontend/src/components/ChatHeader.tsx @@ -6,6 +6,7 @@ import { ContactPathDiscoveryModal } from './ContactPathDiscoveryModal'; import { ChannelFloodScopeOverrideModal } from './ChannelFloodScopeOverrideModal'; import { isFavorite } from '../utils/favorites'; import { handleKeyboardActivate } from '../utils/a11y'; +import { isPublicChannelKey } from '../utils/publicChannel'; import { stripRegionScopePrefix } from '../utils/regionScope'; import { isPrefixOnlyContact } from '../utils/pubkey'; import { ContactAvatar } from './ContactAvatar'; @@ -379,7 +380,7 @@ export function ChatHeader({ )} )} - {!(conversation.type === 'channel' && conversation.name === 'Public') && ( + {!(conversation.type === 'channel' && isPublicChannelKey(conversation.id)) && (