feat: implement API versioning /api/v1/
- All backend routers moved to /api/v1/ prefix
- Frontend BASE_URL updated to /api/v1
- Setup redirect middleware updated to redirect to /api/v1/setup
- Health router path fixed: prefix=/api/v1/health, @router.get('')
- conftest.py: set server_status=online for test fixture
- Created Docs/API_VERSIONING.md with deprecation policy
- Updated Docs/Backend-Development.md with versioning section
- Updated Instructions.md curl examples
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -26,14 +26,14 @@ _SETUP_PAYLOAD = {
|
||||
|
||||
async def _do_setup(client: AsyncClient) -> None:
|
||||
"""Run the setup wizard so auth endpoints are reachable."""
|
||||
resp = await client.post("/api/setup", json=_SETUP_PAYLOAD)
|
||||
resp = await client.post("/api/v1/setup", json=_SETUP_PAYLOAD)
|
||||
assert resp.status_code == 201
|
||||
|
||||
|
||||
async def _login(client: AsyncClient, password: str = "Mysecretpass1!") -> str:
|
||||
"""Helper: perform login and return the session token."""
|
||||
resp = await client.post(
|
||||
"/api/auth/login",
|
||||
"/api/v1/auth/login",
|
||||
json={"password": password},
|
||||
headers={"X-BanGUI-Request": "1"},
|
||||
)
|
||||
@@ -58,7 +58,7 @@ class TestCsrfProtection:
|
||||
|
||||
# POST with correct CSRF header should succeed (endpoint may fail for other reasons)
|
||||
response = await client.post(
|
||||
"/api/auth/logout",
|
||||
"/api/v1/auth/logout",
|
||||
cookies={SESSION_COOKIE_NAME: token},
|
||||
headers={"X-BanGUI-Request": "1"},
|
||||
)
|
||||
@@ -74,7 +74,7 @@ class TestCsrfProtection:
|
||||
|
||||
# POST without CSRF header should be rejected
|
||||
response = await client.post(
|
||||
"/api/auth/logout",
|
||||
"/api/v1/auth/logout",
|
||||
cookies={SESSION_COOKIE_NAME: token},
|
||||
headers={}, # Explicitly omit X-BanGUI-Request
|
||||
)
|
||||
@@ -92,7 +92,7 @@ class TestCsrfProtection:
|
||||
|
||||
# POST with wrong CSRF header value should be rejected
|
||||
response = await client.post(
|
||||
"/api/auth/logout",
|
||||
"/api/v1/auth/logout",
|
||||
cookies={SESSION_COOKIE_NAME: token},
|
||||
headers={"X-BanGUI-Request": "invalid"},
|
||||
)
|
||||
@@ -107,7 +107,7 @@ class TestCsrfProtection:
|
||||
|
||||
# POST with Bearer token but no CSRF header should succeed
|
||||
response = await client.post(
|
||||
"/api/auth/logout",
|
||||
"/api/v1/auth/logout",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
)
|
||||
# Expect 200 (logout succeeds) not 403 (CSRF check should be skipped)
|
||||
@@ -122,7 +122,7 @@ class TestCsrfProtection:
|
||||
|
||||
# GET without CSRF header should succeed (safe method)
|
||||
response = await client.get(
|
||||
"/api/auth/session",
|
||||
"/api/v1/auth/session",
|
||||
cookies={SESSION_COOKIE_NAME: token},
|
||||
headers={}, # Explicitly omit X-BanGUI-Request
|
||||
)
|
||||
@@ -138,7 +138,7 @@ class TestCsrfProtection:
|
||||
|
||||
# OPTIONS without CSRF header should succeed (safe method)
|
||||
response = await client.options(
|
||||
"/api/auth/session",
|
||||
"/api/v1/auth/session",
|
||||
cookies={SESSION_COOKIE_NAME: token},
|
||||
headers={},
|
||||
)
|
||||
@@ -154,7 +154,7 @@ class TestCsrfProtection:
|
||||
|
||||
# HEAD without CSRF header should succeed (safe method)
|
||||
response = await client.head(
|
||||
"/api/auth/session",
|
||||
"/api/v1/auth/session",
|
||||
cookies={SESSION_COOKIE_NAME: token},
|
||||
headers={},
|
||||
)
|
||||
@@ -172,7 +172,7 @@ class TestCsrfProtection:
|
||||
# The endpoint may fail for other reasons (no ban to delete), but not 403 CSRF
|
||||
response = await client.request(
|
||||
"DELETE",
|
||||
"/api/bans",
|
||||
"/api/v1/bans",
|
||||
content='{"ip": "192.0.2.1", "jail": "sshd"}',
|
||||
cookies={SESSION_COOKIE_NAME: token},
|
||||
headers={"X-BanGUI-Request": "1"},
|
||||
@@ -190,7 +190,7 @@ class TestCsrfProtection:
|
||||
# DELETE without CSRF header should be rejected
|
||||
response = await client.request(
|
||||
"DELETE",
|
||||
"/api/bans",
|
||||
"/api/v1/bans",
|
||||
content='{"ip": "192.0.2.1", "jail": "sshd"}',
|
||||
cookies={SESSION_COOKIE_NAME: token},
|
||||
headers={},
|
||||
@@ -206,7 +206,7 @@ class TestCsrfProtection:
|
||||
|
||||
# PUT with correct CSRF header should not be rejected by CSRF middleware
|
||||
response = await client.put(
|
||||
"/api/blocklists/schedule",
|
||||
"/api/v1/blocklists/schedule",
|
||||
json={"enabled": False},
|
||||
cookies={SESSION_COOKIE_NAME: token},
|
||||
headers={"X-BanGUI-Request": "1"},
|
||||
@@ -223,7 +223,7 @@ class TestCsrfProtection:
|
||||
|
||||
# PUT without CSRF header should be rejected
|
||||
response = await client.put(
|
||||
"/api/blocklists/schedule",
|
||||
"/api/v1/blocklists/schedule",
|
||||
json={"enabled": False},
|
||||
cookies={SESSION_COOKIE_NAME: token},
|
||||
headers={},
|
||||
@@ -240,7 +240,7 @@ class TestCsrfProtection:
|
||||
# PATCH with correct CSRF header should not be rejected by CSRF middleware
|
||||
# (endpoint may not exist, but CSRF check should pass)
|
||||
response = await client.patch(
|
||||
"/api/auth/logout",
|
||||
"/api/v1/auth/logout",
|
||||
cookies={SESSION_COOKIE_NAME: token},
|
||||
headers={"X-BanGUI-Request": "1"},
|
||||
)
|
||||
@@ -256,7 +256,7 @@ class TestCsrfProtection:
|
||||
|
||||
# PATCH without CSRF header should be rejected
|
||||
response = await client.patch(
|
||||
"/api/auth/logout",
|
||||
"/api/v1/auth/logout",
|
||||
cookies={SESSION_COOKIE_NAME: token},
|
||||
headers={},
|
||||
)
|
||||
@@ -271,7 +271,7 @@ class TestCsrfProtection:
|
||||
# POST without any authentication should bypass CSRF check
|
||||
# (the endpoint itself will reject it with 401, not 403)
|
||||
response = await client.post(
|
||||
"/api/auth/logout",
|
||||
"/api/v1/auth/logout",
|
||||
headers={},
|
||||
)
|
||||
# Should be 401 (auth required) not 403 (CSRF failed)
|
||||
@@ -289,7 +289,7 @@ class TestCsrfProtection:
|
||||
# POST with Bearer token via Authorization header and no CSRF header
|
||||
# should NOT be rejected by CSRF middleware
|
||||
response = await client.post(
|
||||
"/api/auth/logout",
|
||||
"/api/v1/auth/logout",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
)
|
||||
# Should succeed (200) not fail with 403
|
||||
|
||||
Reference in New Issue
Block a user