fix(webhooks): authenticate deliveries via WEBHOOK_SECRET; review nits
Adds optional shared-secret authentication for /webhooks/nextcloud, addressing the security follow-up flagged in #747. Behavior: - WEBHOOK_SECRET set: registrations pass authMethod="header" with authData={"Authorization": "Bearer <secret>"} (encrypted at-rest in Nextcloud's DB and forwarded on every delivery). The receiver validates the same header with hmac.compare_digest before parsing any payload; missing/invalid → 401. - WEBHOOK_SECRET unset: registrations stay on authMethod="none" and the receiver accepts unauthenticated POSTs (logging a one-time startup warning). Backward compatible — operators can roll out at their own pace. Implementation notes: - WebhooksClient.create_webhook gains an `auth_data` parameter mapped to NC's `authData` body field; this is distinct from the existing `headers` parameter (`headers` is plaintext static request headers, `authData` is encrypted at-rest in NC and only emitted when authMethod="header"). The previous `auth_method="bearer"` mention in the docstring was incorrect — NC supports only "none" and "header". - A small `webhook_auth_pair()` helper in auth/webhook_routes.py centralises the secret→(auth_method, auth_data) resolution so the preset flow and the Astrolabe-facing /api/v1/webhooks endpoint stay in sync. Also addresses the smaller review points from #747: - f-string → lazy %s formatting in webhook_receiver.py and webhook_routes.py. - Move `int(time)` inside webhook_parser's try/except so a malformed `time` field returns None instead of raising ValueError. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
2e2a098bee
commit
224428fca5
@@ -38,8 +38,9 @@ def extract_document_task(payload: dict) -> DocumentTask | None:
|
||||
event = payload["event"]
|
||||
event_class = event["class"]
|
||||
user_id = payload["user"]["uid"]
|
||||
except (KeyError, TypeError):
|
||||
logger.debug("Webhook payload missing user/event/class fields")
|
||||
time = int(payload.get("time", 0) or 0)
|
||||
except (KeyError, TypeError, ValueError):
|
||||
logger.debug("Webhook payload has missing or malformed envelope fields")
|
||||
return None
|
||||
|
||||
if event_class in (
|
||||
@@ -47,7 +48,7 @@ def extract_document_task(payload: dict) -> DocumentTask | None:
|
||||
_FILE_EVENT_WRITTEN,
|
||||
_FILE_EVENT_BEFORE_DELETED,
|
||||
):
|
||||
return _parse_file_event(event_class, event, user_id, payload.get("time", 0))
|
||||
return _parse_file_event(event_class, event, user_id, time)
|
||||
|
||||
logger.debug("Ignoring webhook for unsupported event: %s", event_class)
|
||||
return None
|
||||
@@ -82,5 +83,5 @@ def _parse_file_event(
|
||||
doc_id=str(node_id),
|
||||
doc_type="note",
|
||||
operation=operation,
|
||||
modified_at=int(time),
|
||||
modified_at=time,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user