Allow deleting key expiry

This commit is contained in:
yuneng-jiang
2025-12-19 18:04:04 -08:00
parent 4ca211a6f7
commit ffcac2eebc
4 changed files with 48 additions and 5 deletions
@@ -507,7 +507,11 @@ async def _common_key_generation_helper( # noqa: PLR0915
upperbound_duration = duration_in_seconds(
duration=upperbound_value
)
user_duration = duration_in_seconds(duration=value)
# Handle special case where duration is "-1" (never expires)
if value == "-1":
user_duration = float('inf') # Infinite duration
else:
user_duration = duration_in_seconds(duration=value)
if user_duration > upperbound_duration:
raise HTTPException(
status_code=400,
@@ -1339,7 +1343,10 @@ async def prepare_key_update_data(
if "duration" in non_default_values:
duration = non_default_values.pop("duration")
if duration and (isinstance(duration, str)) and len(duration) > 0:
if duration == "-1":
# Set expires to None to indicate the key never expires
non_default_values["expires"] = None
elif duration and (isinstance(duration, str)) and len(duration) > 0:
duration_s = duration_in_seconds(duration=duration)
expires = datetime.now(timezone.utc) + timedelta(seconds=duration_s)
non_default_values["expires"] = expires
@@ -1452,7 +1459,7 @@ async def update_key_fn(
- tpm_limit_type: Optional[str] - TPM rate limit type - "best_effort_throughput", "guaranteed_throughput", or "dynamic"
- rpm_limit_type: Optional[str] - RPM rate limit type - "best_effort_throughput", "guaranteed_throughput", or "dynamic"
- allowed_cache_controls: Optional[list] - List of allowed cache control values
- duration: Optional[str] - Key validity duration ("30d", "1h", etc.)
- duration: Optional[str] - Key validity duration ("30d", "1h", etc.) or "-1" to never expire
- permissions: Optional[dict] - Key-specific permissions
- send_invite_email: Optional[bool] - Send invite email to user_id
- guardrails: Optional[List[str]] - List of active guardrails for the key
@@ -678,6 +678,11 @@ async def test_prepare_key_update_data():
updated_data = await prepare_key_update_data(data, existing_key_row)
assert updated_data["metadata"] is None
# Test duration "-1" sets expires to None (never expires)
data = UpdateKeyRequest(key="test_key", duration="-1")
updated_data = await prepare_key_update_data(data, existing_key_row)
assert updated_data["expires"] is None
@pytest.mark.parametrize(
"env_vars, expected_url",
@@ -815,6 +815,37 @@ async def test_update_service_account_works_with_team_id():
await prepare_key_update_data(data=data, existing_key_row=existing_key)
@pytest.mark.asyncio
async def test_prepare_key_update_data_duration_never_expires():
"""Test that duration="-1" sets expires to None (never expires)."""
from litellm.proxy._types import UpdateKeyRequest
from litellm.proxy.management_endpoints.key_management_endpoints import (
prepare_key_update_data,
)
# Mock existing key
existing_key = LiteLLM_VerificationToken(
token="test-token",
key_alias="test-key",
models=["gpt-3.5-turbo"],
user_id="test-user",
team_id=None,
auto_rotate=False,
rotation_interval=None,
metadata={},
)
# Test setting duration to "-1" (never expires)
update_request = UpdateKeyRequest(key="test-token", duration="-1")
result = await prepare_key_update_data(
data=update_request, existing_key_row=existing_key
)
# Verify that expires is set to None
assert result["expires"] is None
@pytest.mark.asyncio
async def test_validate_team_id_used_in_service_account_request_requires_team_id():
"""
@@ -64,13 +64,13 @@ const KeyLifecycleSettings: React.FC<KeyLifecycleSettingsProps> = ({
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700 flex items-center space-x-1">
<span>Expire Key</span>
<Tooltip title="Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days)">
<Tooltip title="Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Use -1 to never expire.">
<InfoCircleOutlined className="text-gray-400 cursor-help text-xs" />
</Tooltip>
</label>
<TextInput
name="duration"
placeholder="e.g., 30d"
placeholder="e.g., 30d or -1 to never expire"
className="w-full"
value={durationValue}
onValueChange={handleDurationChange}