diff --git a/src/cmdforge/registry/app.py b/src/cmdforge/registry/app.py index f433fc8..5f6f1c2 100644 --- a/src/cmdforge/registry/app.py +++ b/src/cmdforge/registry/app.py @@ -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/", 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//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: diff --git a/src/cmdforge/web/templates/admin/publishers.html b/src/cmdforge/web/templates/admin/publishers.html index 289b58e..46d3612 100644 --- a/src/cmdforge/web/templates/admin/publishers.html +++ b/src/cmdforge/web/templates/admin/publishers.html @@ -72,7 +72,9 @@ {% else %} {% endif %} - + + + {% endif %} @@ -138,6 +140,49 @@ + + + + + + {% endblock %}