mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-08-06 17:02:59 +02:00
feat(routes): allow operators to manage routes
Operators can now create, edit, and delete routes (previously admin-only). A user may never scope a route above their own role tier: an operator creating/editing an admin-visibility route is rejected (403 on the visibility value, 404 on touching an existing higher-visibility route), preventing them from creating routes they could then never see or modify. - routes.py: RequireAdmin -> RequireOperatorOrAdmin on create/update/delete; add visibility-cap enforcement helpers reusing the existing resolve_user_role / VISIBILITY_LEVELS ladder - web/app.py: proxy access map admits operator for routes POST/PUT/DELETE - Routes.tsx: canManage gate (admin||operator) on Add/Edit/Delete; visibility <select> filters options by caller tier so operators never see 'admin' - tests: operator-tier coverage (create/update/delete at/below/above level), proxy access-map assertion, vitest role-gating + filtered select - e2e: mint operator session + routes-operator spec - docs: routes.md + auth.md operator/visibility-cap notes
This commit is contained in:
@@ -1552,7 +1552,7 @@ class TestMutationInvalidationIntegration:
|
||||
"node_public_keys": [n1.public_key, n2.public_key],
|
||||
"match_width": 2,
|
||||
},
|
||||
headers={"X-User-Roles": "admin"},
|
||||
headers={"X-User-Id": "test-admin", "X-User-Roles": "admin"},
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
mock_cache.delete.assert_any_call("/api/v1/routes")
|
||||
@@ -1585,7 +1585,7 @@ class TestMutationInvalidationIntegration:
|
||||
resp = client_no_auth.put(
|
||||
f"/api/v1/routes/{route.id}",
|
||||
json={"from_label": "NewFrom", "to_label": "NewTo"},
|
||||
headers={"X-User-Roles": "admin"},
|
||||
headers={"X-User-Id": "test-admin", "X-User-Roles": "admin"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
mock_cache.delete.assert_any_call("/api/v1/routes")
|
||||
@@ -1614,7 +1614,7 @@ class TestMutationInvalidationIntegration:
|
||||
mock_cache = self._install_mock_cache(client_no_auth)
|
||||
resp = client_no_auth.delete(
|
||||
f"/api/v1/routes/{route.id}",
|
||||
headers={"X-User-Roles": "admin"},
|
||||
headers={"X-User-Id": "test-admin", "X-User-Roles": "admin"},
|
||||
)
|
||||
assert resp.status_code == 204
|
||||
mock_cache.delete.assert_any_call("/api/v1/routes")
|
||||
@@ -1782,7 +1782,10 @@ class TestMutationVisibilityThroughHttpCache:
|
||||
client_no_auth.app.state.redis_cache_ttl = 30
|
||||
|
||||
# 1) Initial GET — populates cache and returns an ETag.
|
||||
first = client_no_auth.get("/api/v1/routes", headers={"X-User-Roles": "admin"})
|
||||
first = client_no_auth.get(
|
||||
"/api/v1/routes",
|
||||
headers={"X-User-Id": "test-admin", "X-User-Roles": "admin"},
|
||||
)
|
||||
assert first.status_code == 200
|
||||
assert first.headers["x-cache"] == "MISS"
|
||||
assert first.headers["cache-control"] == "private, no-cache"
|
||||
@@ -1807,7 +1810,7 @@ class TestMutationVisibilityThroughHttpCache:
|
||||
mut = client_no_auth.put(
|
||||
f"/api/v1/routes/{route.id}",
|
||||
json={"from_label": "NewOrigin", "to_label": "NewDest"},
|
||||
headers={"X-User-Roles": "admin"},
|
||||
headers={"X-User-Id": "test-admin", "X-User-Roles": "admin"},
|
||||
)
|
||||
assert mut.status_code == 200
|
||||
# Mutations are always no-store.
|
||||
|
||||
Reference in New Issue
Block a user