feat(email): generic webhook for all notification emails, hide SMTP UI
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled

- Renamed the password-setup webhook (email_webhook.*, /system/email-webhook-config)
  and made it the single transport for all notification emails.
- New webhook_email_service.py shared by password_setup_service and
  notification_service.
- Wired test validated/rejected, campaign completed, and new-MITRE-technique
  notifications through the webhook (previously dead SMTP code, never triggered).
- Added POST /system/email-webhook-test to send a real test email.
- Hid the Email/SMTP settings tab from the UI (SMTP code kept intact, unused).
- Redact email_webhook.api_key on config export.
This commit is contained in:
kitos
2026-07-20 10:24:15 +02:00
parent bbe7f49c86
commit 1d0d880929
11 changed files with 442 additions and 146 deletions
+1
View File
@@ -41,6 +41,7 @@ _REDACTED_KEYS = {
"smtp.password",
"jira.admin_api_token",
"tempo.admin_token",
"email_webhook.api_key",
# Older/alternate key names kept defensively in case a prior schema
# version wrote under these instead.
"jira.api_token",
+56 -23
View File
@@ -344,60 +344,93 @@ def update_jira_config(
)
class PasswordWebhookConfigOut(BaseModel):
class EmailWebhookConfigOut(BaseModel):
configured: bool
url: str
api_key_set: bool
class PasswordWebhookConfigUpdate(BaseModel):
class EmailWebhookConfigUpdate(BaseModel):
url: str
# Optional — omit or send "" to leave the currently-stored key unchanged.
api_key: Optional[str] = None
@router.get("/password-webhook-config", response_model=PasswordWebhookConfigOut)
def get_password_webhook_config(
class EmailWebhookTestRequest(BaseModel):
to: str
@router.get("/email-webhook-config", response_model=EmailWebhookConfigOut)
def get_email_webhook_config(
db: Session = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""Return the configured webhook URL used to email set-password links.
"""Return the configured webhook used to send every platform notification
email (password setup/reset, test validated, campaign completed, etc.).
The API key itself is never returned, only whether one is set.
**Requires** the ``admin`` role.
"""
from app.services.password_setup_service import get_password_webhook_api_key, get_password_webhook_url
from app.services.webhook_email_service import get_webhook_api_key, get_webhook_url
url = get_password_webhook_url(db) or ""
api_key_set = bool(get_password_webhook_api_key(db))
return PasswordWebhookConfigOut(configured=bool(url), url=url, api_key_set=api_key_set)
url = get_webhook_url(db) or ""
api_key_set = bool(get_webhook_api_key(db))
return EmailWebhookConfigOut(configured=bool(url), url=url, api_key_set=api_key_set)
@router.patch("/password-webhook-config", response_model=PasswordWebhookConfigOut)
def update_password_webhook_config(
payload: PasswordWebhookConfigUpdate,
@router.patch("/email-webhook-config", response_model=EmailWebhookConfigOut)
def update_email_webhook_config(
payload: EmailWebhookConfigUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""Set the webhook URL (and optionally API key) used to email set-password links.
"""Set the webhook URL (and optionally API key) used to send all
platform notification emails.
**Requires** the ``admin`` role.
"""
from app.services.password_setup_service import (
get_password_webhook_api_key,
get_password_webhook_url,
set_password_webhook_api_key,
set_password_webhook_url,
from app.services.webhook_email_service import (
get_webhook_api_key,
get_webhook_url,
set_webhook_api_key,
set_webhook_url,
)
set_password_webhook_url(db, payload.url)
set_webhook_url(db, payload.url)
if payload.api_key:
set_password_webhook_api_key(db, payload.api_key)
set_webhook_api_key(db, payload.api_key)
db.commit()
url = get_password_webhook_url(db) or ""
api_key_set = bool(get_password_webhook_api_key(db))
return PasswordWebhookConfigOut(configured=bool(url), url=url, api_key_set=api_key_set)
url = get_webhook_url(db) or ""
api_key_set = bool(get_webhook_api_key(db))
return EmailWebhookConfigOut(configured=bool(url), url=url, api_key_set=api_key_set)
@router.post("/email-webhook-test")
def test_email_webhook(
payload: EmailWebhookTestRequest,
db: Session = Depends(get_db),
current_user: User = Depends(require_role("admin")),
) -> dict:
"""Send a real test notification through the configured email webhook.
**Requires** the ``admin`` role.
"""
from app.services.webhook_email_service import send_webhook_email
sent = send_webhook_email(
db,
to=payload.to,
subject="Aegis Email Webhook Test",
message=(
"This is a test notification confirming the email webhook is "
"configured and working correctly."
),
full_name=current_user.full_name,
)
if not sent:
raise HTTPException(status_code=502, detail="Failed to send — check the webhook URL/API key and logs.")
return {"detail": f"Test email sent to {payload.to}"}
@router.post("/jira-test")