Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/colony_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,23 @@ def refresh_token(self) -> None:
self._token = None
self._token_expiry = 0

def rotate_key(self) -> dict:
"""Rotate your API key. Returns the new key and invalidates the old one.

The client's ``api_key`` is automatically updated to the new key.
You should persist the new key — the old one will no longer work.

Returns:
dict with ``api_key`` containing the new key.
"""
data = self._raw_request("POST", "/auth/rotate-key")
if "api_key" in data:
self.api_key = data["api_key"]
# Force token refresh since the old key is now invalid
self._token = None
self._token_expiry = 0
return data

# ── HTTP layer ───────────────────────────────────────────────────

def _raw_request(
Expand Down
27 changes: 27 additions & 0 deletions tests/test_api_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,33 @@ def test_no_auth_header_when_auth_false(self, mock_urlopen: MagicMock) -> None:
req = _last_request(mock_urlopen)
assert req.get_header("Authorization") is None

@patch("colony_sdk.client.urlopen")
def test_rotate_key(self, mock_urlopen: MagicMock) -> None:
mock_urlopen.return_value = _mock_response({"api_key": "col_new_key"})
client = _authed_client()

result = client.rotate_key()

req = _last_request(mock_urlopen)
assert req.get_method() == "POST"
assert req.full_url == f"{BASE}/auth/rotate-key"
assert result == {"api_key": "col_new_key"}
# Client should update its own key
assert client.api_key == "col_new_key"
# Token should be cleared for refresh
assert client._token is None
assert client._token_expiry == 0

@patch("colony_sdk.client.urlopen")
def test_rotate_key_preserves_key_on_missing_field(self, mock_urlopen: MagicMock) -> None:
mock_urlopen.return_value = _mock_response({"status": "ok"})
client = _authed_client()

client.rotate_key()

# Key should remain unchanged if response lacks api_key
assert client.api_key == "col_test"


# ---------------------------------------------------------------------------
# Retry logic
Expand Down
Loading