Skip to content

Commit 99bbbcf

Browse files
Merge pull request #258 from ChrispyBacon-dev/unstable
Enhanced API Key Management
2 parents 7dcf2f0 + 61e298e commit 99bbbcf

5 files changed

Lines changed: 422 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
---
99

10+
## [v3.0.2] - 2025-09-30
1011

11-
Of course, my apologies for the misunderstanding. Here is the changelog with a new, cleanly formatted entry for the hotfix on September 27th, while keeping the September 26th entry separate as you requested.
12+
### Added
13+
- **Enhanced API Key Management**
14+
- **Revoked Key Visibility:** Revoked API keys are now displayed in a separate "Revoked Keys" section with full key visibility for verification and audit purposes.
15+
- **Permanent Deletion:** Added "Delete Permanently" functionality for individual revoked keys and "Clear All" for bulk removal.
16+
- **Auto-Cleanup System:** Implemented automatic cleanup of revoked keys after 30 days with manual trigger option.
17+
- **Improved UX:** Revoked keys are visually distinguished (grayed out, full key shown) with countdown to auto-deletion.
18+
- **Copy Functionality:** Users can copy full revoked API keys for record-keeping before permanent deletion.
19+
20+
### Fixed
21+
- **API Key Revocation Display Bug:** Fixed issue where revoked API keys remained visible in the frontend as if they were active, even though backend authentication correctly rejected them.
1222

1323
---
24+
1425
## [v3.0.1] (Hotfixes) - 2025-09-27
1526

1627
### Added

dockflare/app/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import logging
2121

2222
# --- DockFlare Version ---
23-
APP_VERSION = "v3.0.1"
23+
APP_VERSION = "v3.0.2"
2424
# --- web: https://dockflare.app ---
2525
# --- github: https://github.com/ChrispyBacon-dev/DockFlare ---
2626

dockflare/app/core/state_manager.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,110 @@ def list_agent_keys():
323323
"""Return a shallow copy of the agent key metadata from the encrypted store."""
324324
return agent_key_store.list_keys()
325325

326+
def cleanup_expired_revoked_keys(retention_days=30):
327+
"""
328+
Auto-cleanup revoked keys older than retention_days.
329+
Returns dict with cleanup results.
330+
"""
331+
if retention_days <= 0:
332+
return {"status": "skipped", "message": "Auto-cleanup disabled"}
333+
334+
all_keys = agent_key_store.list_keys()
335+
now = datetime.utcnow().replace(tzinfo=timezone.utc)
336+
337+
expired_keys = []
338+
cleaned_count = 0
339+
340+
for key_id, key_info in all_keys.items():
341+
if key_info.get("status") != "revoked":
342+
continue
343+
344+
revoked_at_str = key_info.get("revoked_at")
345+
if not revoked_at_str:
346+
continue
347+
348+
try:
349+
# Parse revocation timestamp
350+
if revoked_at_str.endswith('Z'):
351+
revoked_at = datetime.fromisoformat(revoked_at_str.replace('Z', '+00:00'))
352+
else:
353+
revoked_at = datetime.fromisoformat(revoked_at_str)
354+
revoked_at = revoked_at.replace(tzinfo=timezone.utc) if revoked_at.tzinfo is None else revoked_at.astimezone(timezone.utc)
355+
356+
# Check if key is expired
357+
days_since_revoked = (now - revoked_at).days
358+
if days_since_revoked >= retention_days:
359+
owner = key_info.get("owner", "unknown")
360+
expired_keys.append({
361+
"key_id": key_id,
362+
"owner": owner,
363+
"revoked_at": revoked_at_str,
364+
"days_old": days_since_revoked
365+
})
366+
367+
# Remove the expired key
368+
agent_key_store.remove_key(key_id)
369+
cleaned_count += 1
370+
logging.info(f"AUTO_CLEANUP: Removed expired revoked key {key_id[:8]}... (owner: {owner}, revoked {days_since_revoked} days ago)")
371+
372+
except Exception as e:
373+
logging.warning(f"AUTO_CLEANUP: Failed to process revoked key {key_id[:8]}: {e}")
374+
375+
result = {
376+
"status": "completed",
377+
"cleaned_count": cleaned_count,
378+
"retention_days": retention_days,
379+
"expired_keys": expired_keys
380+
}
381+
382+
if cleaned_count > 0:
383+
logging.info(f"AUTO_CLEANUP: Removed {cleaned_count} expired revoked keys (retention: {retention_days} days)")
384+
385+
return result
386+
387+
def get_revoked_keys_summary():
388+
"""
389+
Get summary information about revoked keys for display.
390+
Returns dict with revoked key counts and aging info.
391+
"""
392+
all_keys = agent_key_store.list_keys()
393+
now = datetime.utcnow().replace(tzinfo=timezone.utc)
394+
395+
revoked_keys = []
396+
for key_id, key_info in all_keys.items():
397+
if key_info.get("status") != "revoked":
398+
continue
399+
400+
revoked_at_str = key_info.get("revoked_at")
401+
days_until_cleanup = None
402+
403+
if revoked_at_str:
404+
try:
405+
if revoked_at_str.endswith('Z'):
406+
revoked_at = datetime.fromisoformat(revoked_at_str.replace('Z', '+00:00'))
407+
else:
408+
revoked_at = datetime.fromisoformat(revoked_at_str)
409+
revoked_at = revoked_at.replace(tzinfo=timezone.utc) if revoked_at.tzinfo is None else revoked_at.astimezone(timezone.utc)
410+
411+
# Calculate days until auto-cleanup (assuming 30 day retention)
412+
days_since_revoked = (now - revoked_at).days
413+
days_until_cleanup = max(0, 30 - days_since_revoked)
414+
415+
except Exception:
416+
pass
417+
418+
revoked_keys.append({
419+
"key_id": key_id,
420+
"owner": key_info.get("owner", "unknown"),
421+
"revoked_at": revoked_at_str,
422+
"days_until_cleanup": days_until_cleanup
423+
})
424+
425+
return {
426+
"revoked_count": len(revoked_keys),
427+
"revoked_keys": revoked_keys
428+
}
429+
326430
def get_agent_rules(agent_id):
327431
"""Return all active rules for a specific agent."""
328432
with state_lock:

0 commit comments

Comments
 (0)