feat: add ACME account management with validation pipeline
- Register, view, and deactivate ACME accounts via API and UI - 16-check validation framework for certificate issuance readiness - DNS resolution, port, nginx, and firewall pre-flight checks - External IP detection with NAT support and fallback providers - Account card and settings modal in certificates page - Guard certificate issuance behind account registration - Update modal CSS to overlay-based approach - 1000+ lines of tests for validation and account handlers
This commit is contained in:
+61
-4
@@ -9,10 +9,13 @@ from flask import Blueprint, request
|
||||
|
||||
from daemon.client import BadRequest, NotFound, delete, get, post
|
||||
from daemon.iface import (
|
||||
DELETE_ACME_ACCOUNT_DEACTIVATE,
|
||||
DELETE_ACME_REMOVE,
|
||||
GET_ACME_ACCOUNT,
|
||||
GET_ACME_INFO,
|
||||
GET_ACME_ISSUE_STATUS,
|
||||
GET_ACME_LIST,
|
||||
POST_ACME_ACCOUNT_REGISTER,
|
||||
POST_ACME_EMAIL,
|
||||
POST_ACME_ISSUE,
|
||||
POST_ACME_RENEW,
|
||||
@@ -68,7 +71,7 @@ def validate():
|
||||
Response containing validation results or an error message.
|
||||
"""
|
||||
body = request.get_json(silent=True) or {}
|
||||
domain = body.get("domain", "").strip()
|
||||
domain = (body.get("domain") or "").strip()
|
||||
if not domain:
|
||||
return _error("'domain' is required", 400)
|
||||
try:
|
||||
@@ -92,10 +95,10 @@ def issue_start():
|
||||
Response containing an issuance request ID or an error message.
|
||||
"""
|
||||
body = request.get_json(silent=True) or {}
|
||||
domain = body.get("domain", "").strip()
|
||||
domain = (body.get("domain") or "").strip()
|
||||
if not domain:
|
||||
return _error("'domain' is required", 400)
|
||||
email = body.get("email", "").strip() or None
|
||||
email = (body.get("email") or "").strip() or None
|
||||
webroot = body.get("webroot")
|
||||
try:
|
||||
logger.info("Certificate issuance requested for '%s' via API", domain)
|
||||
@@ -192,7 +195,7 @@ def set_email_bp():
|
||||
Response confirming the email was set or an error message.
|
||||
"""
|
||||
body = request.get_json(silent=True) or {}
|
||||
email = body.get("email", "").strip()
|
||||
email = (body.get("email") or "").strip()
|
||||
if not email:
|
||||
return _error("'email' is required", 400)
|
||||
try:
|
||||
@@ -205,3 +208,57 @@ def set_email_bp():
|
||||
except RuntimeError as exc:
|
||||
logger.error("Failed to set ACME email: %s", exc)
|
||||
return _error(str(exc), 500)
|
||||
|
||||
|
||||
@bp.route("/account", methods=["GET"])
|
||||
def account():
|
||||
"""GET /api/certs/account — return ACME account information.
|
||||
|
||||
Returns:
|
||||
Response containing account status or an error message.
|
||||
"""
|
||||
try:
|
||||
result = get(GET_ACME_ACCOUNT)
|
||||
return _ok(result)
|
||||
except RuntimeError as exc:
|
||||
logger.error("Failed to get ACME account: %s", exc)
|
||||
return _error(str(exc), 500)
|
||||
|
||||
|
||||
@bp.route("/account/register", methods=["POST"])
|
||||
def register_account():
|
||||
"""POST /api/certs/account/register — register a new ACME account.
|
||||
|
||||
Expects JSON body with ``{``email``, ``server``?}``.
|
||||
|
||||
Returns:
|
||||
Response confirming registration or an error message.
|
||||
"""
|
||||
body = request.get_json(silent=True) or {}
|
||||
email = (body.get("email") or "").strip()
|
||||
if not email:
|
||||
return _error("'email' is required", 400)
|
||||
server = (body.get("server") or "").strip()
|
||||
try:
|
||||
result = post(POST_ACME_ACCOUNT_REGISTER, {"email": email, "server": server})
|
||||
return _ok(result)
|
||||
except BadRequest as exc:
|
||||
return _error(str(exc), 400)
|
||||
except RuntimeError as exc:
|
||||
logger.error("Failed to register ACME account: %s", exc)
|
||||
return _error(str(exc), 500)
|
||||
|
||||
|
||||
@bp.route("/account", methods=["DELETE"])
|
||||
def deactivate_account():
|
||||
"""DELETE /api/certs/account — deactivate the ACME account.
|
||||
|
||||
Returns:
|
||||
Response confirming deactivation or an error message.
|
||||
"""
|
||||
try:
|
||||
result = delete(DELETE_ACME_ACCOUNT_DEACTIVATE)
|
||||
return _ok(result)
|
||||
except RuntimeError as exc:
|
||||
logger.error("Failed to deactivate ACME account: %s", exc)
|
||||
return _error(str(exc), 500)
|
||||
|
||||
Reference in New Issue
Block a user