From f0248975908ad670fa8a820f865bf22ea8d0172d Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Thu, 13 Aug 2026 03:56:30 +0200 Subject: feat: Phase 12 — P2P crypto material, password split, node Ed25519 auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Baseline commit capturing in-progress Phase 12 work that was already present in the working tree (uncommitted) before the Phase 11.5 security remediation begins. Committed as-is, without review or modification, so that remediation changes arrive as a separable diff. Contents: BundleStore (P2P GEK + keypair bundles), password split (auth_key / bundle_key), node Ed25519 auth (POST /v1/nodes/auth, node-scoped JWT), GEK-HMAC handshake proof with DTLS channel binding, Ed25519 admin challenge-response, node local admin UI rewrite, browser key persistence. Not authored in this session — captured to establish a baseline. Co-Authored-By: Claude Opus 5 --- packages/meshbay-hub/src/meshbay_hub/api/deps.py | 40 ++++++++++++++++++------ 1 file changed, 30 insertions(+), 10 deletions(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/api/deps.py') diff --git a/packages/meshbay-hub/src/meshbay_hub/api/deps.py b/packages/meshbay-hub/src/meshbay_hub/api/deps.py index addba30..1bf57a4 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/deps.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/deps.py @@ -1,5 +1,10 @@ """ FastAPI shared dependencies — injected via Depends(). + +JWT scope enforcement: + - "user" scope (browser login): full access to all endpoints + - "node" scope (Ed25519 daemon auth): read-only group access + node operations + Node-scoped tokens CANNOT create/delete groups or manage membership. """ from fastapi import Depends, Header, HTTPException, status @@ -18,20 +23,13 @@ def set_admin_usernames(usernames: list[str]) -> None: _admin_usernames = set(usernames) -async def get_current_user( - authorization: str = Header(...), - db: AsyncSession = Depends(get_db), -) -> User: - """ - Verify the JWT bearer token and return the User from the database. - Node clients: verified locally with hub PK — no DB round-trip needed. - Hub API (web): must confirm user still exists and is active. - """ +async def _decode_token(authorization: str = Header(...)) -> dict: + """Decode and verify JWT bearer token. Returns full payload.""" try: scheme, token = authorization.split(None, 1) if scheme.lower() != "bearer": raise ValueError - payload = decode_access_token(token) + return decode_access_token(token) except Exception: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -39,6 +37,15 @@ async def get_current_user( headers={"WWW-Authenticate": "Bearer"}, ) + +async def get_current_user( + payload: dict = Depends(_decode_token), + db: AsyncSession = Depends(get_db), +) -> User: + """ + Verify the JWT bearer token and return the User from the database. + Accepts both user-scoped and node-scoped tokens. + """ result = await db.execute( select(User).where(User.id == payload["sub"])) user = result.scalar_one_or_none() @@ -52,6 +59,19 @@ async def get_current_user( return user +async def require_user_scope( + payload: dict = Depends(_decode_token), + current_user: User = Depends(get_current_user), +) -> User: + """Reject node-scoped tokens — only browser (user-scope) can mutate groups.""" + if payload.get("scope") == "node": + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Node-scoped token cannot perform this operation — use browser", + ) + return current_user + + async def require_moderator( current_user: User = Depends(get_current_user), ) -> User: -- cgit v1.2.3