diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-13 03:56:30 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-13 03:56:30 +0200 |
| commit | f0248975908ad670fa8a820f865bf22ea8d0172d (patch) | |
| tree | f4af64d36cacaccb4f6d13436e001aeb57e861e3 /packages/meshbay-hub/src/meshbay_hub/api/deps.py | |
| parent | 35130e5528a52161630fd1c93572e1b2b7cd911b (diff) | |
| download | meshbay-f0248975908ad670fa8a820f865bf22ea8d0172d.tar.gz | |
feat: Phase 12 — P2P crypto material, password split, node Ed25519 auth
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 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/api/deps.py')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/api/deps.py | 40 |
1 files changed, 30 insertions, 10 deletions
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: |