diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/api/groups.py')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/api/groups.py | 37 |
1 files changed, 26 insertions, 11 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/groups.py b/packages/meshbay-hub/src/meshbay_hub/api/groups.py index 88af764..000f3f7 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/groups.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/groups.py @@ -6,6 +6,7 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from meshbay_hub.api.deps import get_current_user, require_user_scope +from meshbay_hub.api.netutil import client_ip from meshbay_hub.db.engine import get_db from meshbay_hub.db.models import ( FederatedGroup, Group, GroupMember, @@ -14,6 +15,10 @@ from meshbay_hub.db.models import ( router = APIRouter(prefix="/v1/groups", tags=["groups"]) +# Swarm endpoints live at /v1/swarm/*. They were previously declared on the groups +# router with a full path, which mounted them at /v1/groups/v1/swarm/* (H7). +swarm_router = APIRouter(prefix="/v1/swarm", tags=["swarm"]) + @router.get("/mine") async def my_groups( @@ -117,13 +122,21 @@ class SwarmRegisterRequest(BaseModel): endpoint: str # "ip:port" -@router.post("/v1/swarm/register", status_code=201) +@swarm_router.post("/register", status_code=201) async def swarm_register( body: SwarmRegisterRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): - """Node registers itself as a source for a content hash (public swarm).""" + """ + Node registers itself as a source for a PUBLIC content hash. + + Finding H7: the node registered hashes for every group it hosted, private ones + included, and this route was mounted at /v1/groups/v1/swarm/register — so the + node's calls 404'd and the leak was masked by a routing bug rather than + prevented. Nodes now filter by group visibility before calling, and the path is + correct, so the filter has to be right. + """ from meshbay_hub.csam import check_content_hash if check_content_hash(body.content_hash): raise HTTPException(status_code=451, detail="Content blocked") @@ -144,12 +157,18 @@ async def swarm_register( return {"status": "registered", "hash": body.content_hash} -@router.get("/v1/swarm/{content_hash}") +@swarm_router.get("/{content_hash}") async def swarm_sources( content_hash: str, + current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): - """Return list of nodes that can serve a content hash.""" + """ + Return nodes that can serve a content hash. + + Authenticated (H7): an open endpoint lets anyone probe whether a given file + exists anywhere in the network and which node holds it. + """ from datetime import datetime, timezone, timedelta cutoff = datetime.now(timezone.utc) - timedelta(minutes=30) result = await db.execute( @@ -214,7 +233,7 @@ async def join_group( db.add(GroupMember(group_id=group_id, user_id=current_user.id)) db.add(IPLog(user_id=current_user.id, event="group_join", - ip_address=_ip(request), detail=group.name)) + ip_address=client_ip(request), detail=group.name)) await db.commit() return {"status": "joined", "group_id": group_id, "name": group.name} @@ -246,7 +265,7 @@ async def create_group( db.add(GroupMember(group_id=group.id, user_id=current_user.id)) db.add(IPLog(user_id=current_user.id, event="group_create", - ip_address=_ip(request), detail=body.name)) + ip_address=client_ip(request), detail=body.name)) await db.commit() await db.refresh(group) return {"group_id": group.id, "name": group.name} @@ -304,13 +323,9 @@ async def delete_group( from sqlalchemy import delete as sa_delete await db.execute(sa_delete(GroupMember).where(GroupMember.group_id == group_id)) db.add(IPLog(user_id=current_user.id, event="group_delete", - ip_address=_ip(request), detail=group.name)) + ip_address=client_ip(request), detail=group.name)) await db.delete(group) await db.commit() return {"status": "deleted", "group_id": group_id} -def _ip(request: Request) -> str: - fwd = request.headers.get("X-Forwarded-For") - return fwd.split(",")[0].strip() if fwd else ( - request.client.host if request.client else "unknown") |