From 399ac5f0da0edfeeb04fc4d65ea826a0bd554086 Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Sat, 11 Oct 2025 18:08:00 +1100 Subject: [PATCH] feat: Move acme to blueprint and cleanup json responses --- blueprints/acme.py | 36 ++++++++++++++++++++++++++++++++++++ server.py | 44 ++------------------------------------------ tools.py | 22 +++++++++++++--------- 3 files changed, 51 insertions(+), 51 deletions(-) create mode 100644 blueprints/acme.py diff --git a/blueprints/acme.py b/blueprints/acme.py new file mode 100644 index 0000000..a51c7b9 --- /dev/null +++ b/blueprints/acme.py @@ -0,0 +1,36 @@ +from flask import Blueprint, request +import os +from cloudflare import Cloudflare +from tools import json_response + +acme_bp = Blueprint('acme', __name__) + + +@acme_bp.route("/hnsdoh-acme", methods=["POST"]) +def acme_post(): + # Get the TXT record from the request + if not request.is_json or not request.json: + return json_response(request, "415 Unsupported Media Type", 415) + if "txt" not in request.json or "auth" not in request.json: + return json_response(request, "400 Bad Request", 400) + + txt = request.json["txt"] + auth = request.json["auth"] + if auth != os.getenv("CF_AUTH"): + return json_response(request, "401 Unauthorized", 401) + + cf = Cloudflare(api_token=os.getenv("CF_TOKEN")) + zone = cf.zones.list(name="hnsdoh.com").to_dict() + zone_id = zone["result"][0]["id"] # type: ignore + existing_records = cf.dns.records.list( + zone_id=zone_id, type="TXT", name="_acme-challenge.hnsdoh.com" # type: ignore + ).to_dict() + record_id = existing_records["result"][0]["id"] # type: ignore + cf.dns.records.delete(dns_record_id=record_id, zone_id=zone_id) + cf.dns.records.create( + zone_id=zone_id, + type="TXT", + name="_acme-challenge", + content=txt, + ) + return json_response(request, "Success", 200) diff --git a/server.py b/server.py index 598291e..12b3b24 100644 --- a/server.py +++ b/server.py @@ -13,7 +13,6 @@ from flask_cors import CORS import os import dotenv import requests -from cloudflare import Cloudflare import datetime import qrcode from qrcode.constants import ERROR_CORRECT_L, ERROR_CORRECT_H @@ -24,6 +23,7 @@ from blueprints.blog import blog_bp from blueprints.wellknown import wk_bp from blueprints.api import api_bp, getGitCommit from blueprints.podcast import podcast_bp +from blueprints.acme import acme_bp from tools import isCurl, isCrawler, getAddress, getFilePath, error_response, getClientIP app = Flask(__name__) @@ -35,6 +35,7 @@ app.register_blueprint(blog_bp, url_prefix='/blog') app.register_blueprint(wk_bp, url_prefix='/.well-known') app.register_blueprint(api_bp, url_prefix='/api/v1') app.register_blueprint(podcast_bp) +app.register_blueprint(acme_bp) dotenv.load_dotenv() @@ -712,47 +713,6 @@ def resume_pdf_get(): return error_response(request, message="Resume not found") # endregion - -# region ACME route - - -@app.route("/hnsdoh-acme", methods=["POST"]) -def acme_post(): - print(f"ACME request from {getClientIP(request)}") - - # Get the TXT record from the request - if not request.json: - print("No JSON data provided for ACME") - return jsonify({"status": "error", "error": "No JSON data provided"}) - if "txt" not in request.json or "auth" not in request.json: - print("Missing required data for ACME") - return jsonify({"status": "error", "error": "Missing required data"}) - - txt = request.json["txt"] - auth = request.json["auth"] - if auth != os.getenv("CF_AUTH"): - print("Invalid auth for ACME") - return jsonify({"status": "error", "error": "Invalid auth"}) - - cf = Cloudflare(api_token=os.getenv("CF_TOKEN")) - zone = cf.zones.list(name="hnsdoh.com").to_dict() - zone_id = zone["result"][0]["id"] # type: ignore - existing_records = cf.dns.records.list( - zone_id=zone_id, type="TXT", name="_acme-challenge.hnsdoh.com" # type: ignore - ).to_dict() - record_id = existing_records["result"][0]["id"] # type: ignore - cf.dns.records.delete(dns_record_id=record_id, zone_id=zone_id) - cf.dns.records.create( - zone_id=zone_id, - type="TXT", - name="_acme-challenge", - content=txt, - ) - print(f"ACME request successful: {txt}") - return jsonify({"status": "success"}) - -# endregion - # region Error Catching # Catch all for GET requests diff --git a/tools.py b/tools.py index a40101c..84b301f 100644 --- a/tools.py +++ b/tools.py @@ -58,15 +58,19 @@ def getFilePath(name, path): if name in files: return os.path.join(root, name) -def error_response(request: Request, message: str = "404 Not Found", code: int = 404): - if isCurl(request): - return jsonify( - { - "status": code, - "message": message, - "ip": getClientIP(request), - } - ), code +def json_response(request: Request, message: str = "404 Not Found", code: int = 404): + return jsonify( + { + "status": code, + "message": message, + "ip": getClientIP(request), + } + ), code + + +def error_response(request: Request, message: str = "404 Not Found", code: int = 404, force_json: bool = False): + if force_json or isCurl(request): + return json_response(request, message, code) # Check if .html exists in templates if os.path.isfile(f"templates/{code}.html"):