Add admin tools: delete publisher and reset password
- DELETE /api/v1/admin/publishers/<id> - delete publisher with optional tool deletion - POST /api/v1/admin/publishers/<id>/reset-password - generate temporary password Safety features: - Cannot delete yourself or other admins - Delete requires typing username to confirm - Reset password shows temp password in modal with copy button Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
@@ -2488,6 +2488,78 @@ def create_app() -> Flask:
|
||||
|
||||
return jsonify({"data": {"status": "updated", "publisher_id": publisher_id, "role": new_role}})
|
||||
|
||||
@app.route("/api/v1/admin/publishers/<int:publisher_id>", methods=["DELETE"])
|
||||
@require_admin
|
||||
def admin_delete_publisher(publisher_id: int) -> Response:
|
||||
"""Delete a publisher and optionally their tools."""
|
||||
delete_tools = request.args.get("delete_tools", "false").lower() == "true"
|
||||
|
||||
publisher = query_one(g.db, "SELECT * FROM publishers WHERE id = ?", [publisher_id])
|
||||
if not publisher:
|
||||
return error_response("PUBLISHER_NOT_FOUND", "Publisher not found", 404)
|
||||
|
||||
# Don't allow deleting yourself
|
||||
if publisher_id == g.current_publisher["id"]:
|
||||
return error_response("CANNOT_DELETE_SELF", "Cannot delete your own account", 400)
|
||||
|
||||
# Don't allow deleting other admins
|
||||
if publisher["role"] == "admin":
|
||||
return error_response("CANNOT_DELETE_ADMIN", "Cannot delete admin accounts", 400)
|
||||
|
||||
slug = publisher["slug"]
|
||||
|
||||
if delete_tools:
|
||||
# Delete all tools owned by this publisher
|
||||
g.db.execute("DELETE FROM tools WHERE publisher_id = ?", [publisher_id])
|
||||
|
||||
# Revoke all tokens
|
||||
g.db.execute("UPDATE api_tokens SET revoked_at = CURRENT_TIMESTAMP WHERE publisher_id = ?", [publisher_id])
|
||||
|
||||
# Delete the publisher
|
||||
g.db.execute("DELETE FROM publishers WHERE id = ?", [publisher_id])
|
||||
g.db.commit()
|
||||
|
||||
log_audit("delete_publisher", "publisher", str(publisher_id), {
|
||||
"slug": slug,
|
||||
"delete_tools": delete_tools,
|
||||
})
|
||||
|
||||
return jsonify({"data": {"status": "deleted", "publisher_id": publisher_id, "slug": slug}})
|
||||
|
||||
@app.route("/api/v1/admin/publishers/<int:publisher_id>/reset-password", methods=["POST"])
|
||||
@require_admin
|
||||
def admin_reset_password(publisher_id: int) -> Response:
|
||||
"""Generate a temporary password for a publisher."""
|
||||
import secrets
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
publisher = query_one(g.db, "SELECT * FROM publishers WHERE id = ?", [publisher_id])
|
||||
if not publisher:
|
||||
return error_response("PUBLISHER_NOT_FOUND", "Publisher not found", 404)
|
||||
|
||||
# Generate a temporary password
|
||||
temp_password = secrets.token_urlsafe(12)
|
||||
password_hash = generate_password_hash(temp_password)
|
||||
|
||||
g.db.execute(
|
||||
"UPDATE publishers SET password_hash = ? WHERE id = ?",
|
||||
[password_hash, publisher_id],
|
||||
)
|
||||
g.db.commit()
|
||||
|
||||
log_audit("reset_password", "publisher", str(publisher_id), {
|
||||
"slug": publisher["slug"],
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
"data": {
|
||||
"status": "reset",
|
||||
"publisher_id": publisher_id,
|
||||
"slug": publisher["slug"],
|
||||
"temporary_password": temp_password,
|
||||
}
|
||||
})
|
||||
|
||||
@app.route("/api/v1/admin/reports", methods=["GET"])
|
||||
@require_moderator
|
||||
def admin_list_reports() -> Response:
|
||||
|
||||
Reference in New Issue
Block a user