fix bug api key managemtn for admin

This commit is contained in:
jigoong
2026-02-25 02:08:34 +07:00
parent 649473d2cc
commit c57755c09c
11 changed files with 126 additions and 46 deletions

View File

@@ -11,7 +11,13 @@ from wtforms.validators import Optional
from app.core.config import settings
from app.db.engine import engine
from app.db.models import ApiClient, ApiKey
from app.security.api_key import generate_api_key, get_prefix, hash_api_key
from app.security.api_key import (
decrypt_api_key,
encrypt_api_key,
generate_api_key,
get_prefix,
hash_api_key,
)
class AdminAuth(AuthenticationBackend):
@@ -38,45 +44,31 @@ class ApiClientAdmin(ModelView, model=ApiClient):
class ApiKeyAdmin(ModelView, model=ApiKey):
form_excluded_columns = [ApiKey.key_hash, ApiKey.key_prefix, ApiKey.created_at]
column_list = [ApiKey.id, ApiKey.client_id, ApiKey.name, ApiKey.key_prefix, ApiKey.is_active, ApiKey.created_at]
column_details_list = [ApiKey.id, ApiKey.client_id, ApiKey.name, ApiKey.key_prefix, ApiKey.permissions, ApiKey.is_active, ApiKey.created_at]
details_template = "apikey_details.html"
form_extra_fields = {
"permissions": TextAreaField(
"Permissions (JSON Array)",
validators=[Optional()],
description='Example: ["feed.waiting-time:write", "feed.opd-checkpoint:write"]',
render_kw={"placeholder": '["feed.waiting-time:write"]', "rows": 3}
),
form_excluded_columns = [ApiKey.key_hash, ApiKey.key_prefix, ApiKey.created_at, ApiKey.encrypted_key]
column_list = [ApiKey.id, ApiKey.client_id, ApiKey.name, ApiKey.key_prefix, ApiKey.encrypted_key, ApiKey.is_active, ApiKey.created_at]
column_details_list = [ApiKey.id, ApiKey.client_id, ApiKey.name, ApiKey.key_prefix, ApiKey.encrypted_key, ApiKey.permissions, ApiKey.is_active, ApiKey.created_at]
edit_template = "apikey_edit.html"
column_formatters = {
ApiKey.encrypted_key: lambda m, a: (m.encrypted_key[:16] + "...") if m.encrypted_key else "-"
}
form_args = {
"permissions": {
"label": "Permissions (JSON Array)",
"description": 'Example: ["feed.waiting-time:write", "feed.opd-checkpoint:write"]'
}
}
async def on_model_change(self, data: dict, model: ApiKey, is_created: bool, request: Request) -> None:
import json
# Handle permissions from textarea (JSON format)
permissions_str = data.get("permissions")
if permissions_str:
try:
perms = json.loads(permissions_str)
if isinstance(perms, list):
model.permissions = perms
else:
raise ValueError("Permissions must be a JSON array")
except (json.JSONDecodeError, ValueError) as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid permissions format: {str(e)}. Use JSON array like: [\"feed.waiting-time:write\"]"
)
# Auto-generate key for new records if not provided
if is_created and not model.key_hash:
plain_key = generate_api_key()
model.key_prefix = get_prefix(plain_key)
model.key_hash = hash_api_key(plain_key)
model.encrypted_key = encrypt_api_key(plain_key)
# Store in session for display after creation
request.session[f"new_api_key_{model.client_id}"] = plain_key
request.session[f"new_api_key_{model.id or 'new'}"] = plain_key
def mount_admin(app):
@@ -128,6 +120,7 @@ def mount_admin(app):
name=name,
key_prefix=get_prefix(plain_key),
key_hash=hash_api_key(plain_key),
encrypted_key=encrypt_api_key(plain_key),
permissions=perms,
is_active=True,
)
@@ -155,6 +148,7 @@ def mount_admin(app):
plain_key = generate_api_key()
api_key.key_prefix = get_prefix(plain_key)
api_key.key_hash = hash_api_key(plain_key)
api_key.encrypted_key = encrypt_api_key(plain_key)
db.commit()
db.refresh(api_key)
@@ -170,7 +164,7 @@ def mount_admin(app):
finally:
db.close()
@app.get("/admin/api-keys/{key_id}/view")
@app.post("/admin/api-keys/{key_id}/view")
async def _admin_view_api_key(request: Request, key_id: int):
"""View API key from session (only works for newly created/regenerated keys)."""
if not request.session.get("admin"):
@@ -192,10 +186,20 @@ def mount_admin(app):
return JSONResponse({
"success": True,
"api_key": plain_key,
"key_prefix": api_key.key_prefix,
"key_prefix": get_prefix(api_key.key_prefix),
"message": "This is the only time you can view this key!"
})
else:
# fallback to encrypted_key if exists
if api_key.encrypted_key:
decrypted = decrypt_api_key(api_key.encrypted_key)
if decrypted:
return JSONResponse({
"success": True,
"api_key": decrypted,
"key_prefix": get_prefix(decrypted),
"message": "Retrieved from encrypted storage."
})
return JSONResponse({
"success": False,
"message": "API key cannot be retrieved. Keys can only be viewed once after creation or regeneration."