Files
ServerKit/backend/tests/test_servers_api_key_scope.py
Juan Denis 93a3cd783e Let GET /api/v1/servers accept a scoped API key
Vela holds one ServerKit API key as a host-side secret and polls the
fleet list, so this read route moves from bare @jwt_required() to
auth_required() + require_scope('servers:read') — a deliberate,
per-route jwt_only_census exception, not a sweep. JWT callers are
unchanged; a restricted key without servers:read gets 403 from the
scope middleware, no credentials get 401.

Scope choice: the existing servers:read catalog entry already covers
'list servers, agents, and metrics', and the serverkit-gui read routes
(capabilities/frame/surface, sibling repo) take the same scope, so one
key scope serves the whole Vela polling surface.

The census ceiling ratchets down to 600 and the migration inventory is
regenerated, as both ratchets require. tests/test_servers_api_key_scope.py
pins the four cases: scoped key 200, wrong-scope key 403, JWT 200,
anonymous 401.
2026-10-03 00:31:36 -04:00

41 lines
1.5 KiB
Python

"""GET /api/v1/servers accepts a scoped API key (deliberate conversion).
A Vela host holds one ServerKit API key as a host-side secret and polls the
fleet list, so this read route takes a JWT *or* an ``X-API-Key`` carrying the
``servers:read`` scope. That is a recorded jwt_only_census exception — JWT-only
stays the default elsewhere (see tests/jwt_only_census.py); these tests pin
the behavior of the exception itself.
"""
from factories import make_user, headers_for
from app.services.api_key_service import ApiKeyService
def _api_key_headers(db, scopes):
user = make_user(db)
_, raw_key = ApiKeyService.create_key(user.id, name='servers-poll', scopes=scopes)
return {'X-API-Key': raw_key}
def test_api_key_with_servers_read_scope_lists_servers(client, db_session):
resp = client.get('/api/v1/servers',
headers=_api_key_headers(db_session, ['servers:read']))
assert resp.status_code == 200
assert isinstance(resp.json, list)
def test_api_key_without_the_scope_is_denied(client, db_session):
resp = client.get('/api/v1/servers',
headers=_api_key_headers(db_session, ['databases:read']))
assert resp.status_code == 403
def test_jwt_caller_still_lists_servers(client, db_session):
resp = client.get('/api/v1/servers',
headers=headers_for(make_user(db_session)))
assert resp.status_code == 200
assert isinstance(resp.json, list)
def test_unauthenticated_gets_401(client):
assert client.get('/api/v1/servers').status_code == 401