Merge pull request #2958 from markparonyan/api-issue-09-aef9as

feat(api): return token lifetimes and document API keys as bearer tokens
This commit is contained in:
Jeremy Grossmann authored and GitHub committed 2026-10-08 04:35:19 +02:00
commit ed8b0c3e20
5 files changed
+97 -21

No files matched your search

+68
View File
@@ -446,6 +446,74 @@ class TestLogout:
assert response.status_code == status.HTTP_401_UNAUTHORIZED
class TestTokenLifetime:
async def _issue(self, app, client, user, endpoint):
credentials = {"username": user.username, "password": "user1_password"}
if endpoint == "login":
return await client.post(
app.url_path_for("login"),
data=credentials,
headers={"content-type": "application/x-www-form-urlencoded"},
)
auth = await client.post(app.url_path_for("authenticate"), json=credentials)
if endpoint == "authenticate":
return auth
return await client.post(
app.url_path_for("refresh_access_token"), json={"refresh_token": auth.json()["refresh_token"]}
)
@pytest.mark.parametrize("endpoint", ("login", "authenticate", "refresh_access_token"))
async def test_token_response_includes_lifetimes(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
config: Config,
endpoint: str,
) -> None:
response = await self._issue(app, unauthorized_client, test_user, endpoint)
assert response.status_code == status.HTTP_200_OK
body = response.json()
controller = config.settings.Controller
assert body["expires_in"] == controller.jwt_access_token_expire_minutes * 60
assert body["refresh_expires_in"] == controller.jwt_refresh_token_expire_minutes * 60
assert body["access_token"]
assert body["token_type"] == "bearer"
assert body["refresh_token"]
@pytest.mark.parametrize("endpoint", ("login", "authenticate", "refresh_access_token"))
async def test_expires_in_matches_jwt_claims(
self,
app: FastAPI,
unauthorized_client: AsyncClient,
test_user: User,
config: Config,
endpoint: str,
) -> None:
response = await self._issue(app, unauthorized_client, test_user, endpoint)
body = response.json()
key = OctKey.import_key(config.settings.Controller.jwt_secret_key)
access = jwt.decode(body["access_token"], key, algorithms=["HS256"]).claims
refresh = jwt.decode(body["refresh_token"], key, algorithms=["HS256"]).claims
assert access["exp"] - access["iat"] == body["expires_in"]
assert refresh["exp"] - refresh["iat"] == body["refresh_expires_in"]
async def test_openapi_declares_token_lifetimes_as_integers(self, app: FastAPI) -> None:
schema = app.openapi()["components"]["schemas"]["Token"]
assert schema["properties"]["expires_in"]["type"] == "integer"
refresh_expires_in = schema["properties"]["refresh_expires_in"]
assert "integer" in str(refresh_expires_in)
assert "expires_in" in schema["required"]
async def test_openapi_security_scheme_mentions_api_keys(self, app: FastAPI) -> None:
schemes = app.openapi()["components"]["securitySchemes"]
assert any("API key" in scheme.get("description", "") for scheme in schemes.values())
class TestRefreshToken:
async def test_login_returns_refresh_token(
self,