e9a7278709
REST API endpoints под /api/v1/admin/webhooks/:
- GET /subscriptions — list (INTERNAL+ scope)
- POST /subscriptions — create (RESTRICTED scope)
- GET /subscriptions/{id} — get one (INTERNAL+)
- PUT /subscriptions/{id} — update (RESTRICTED)
- DELETE /subscriptions/{id} — delete (RESTRICTED)
- POST /subscriptions/{id}/rotate-secret — regenerate HMAC (RESTRICTED)
- GET /subscriptions/{id}/deliveries — delivery history per sub
- GET /deliveries/dlq — DLQ listing
RBAC через ScopeContext: RESTRICTED для mutating, INTERNAL+ для read.
PUBLIC scope получает 403.
DTOs:
- CreateWebhookSubscriptionRequest (Bean Validation: URL regex, length)
- WebhookSubscriptionResponse (с factory `from()` маскированный secret
vs `fromWithSecret()` plaintext только для create response)
- WebhookDeliveryResponse
Service:
- HMAC secret 32 random bytes → 64 hex chars через SecureRandom
- Plaintext secret виден только в create/rotate response (single-time)
- list/get показывают `sk_****<last4>` masked
Tests (15 SsrfGuard + 8 HmacSigner = 23 new):
- HmacSigner: known vector verification, prefix, hex length, distinct
inputs produce distinct sigs, empty secret rejected
- SsrfGuard: rejects 127/8, 169.254/16, 10/8, 192.168/16, 0.0.0.0,
non-http schemes, missing host, unresolvable DNS. Allowlist bypass.
isPrivate() unit tests для loopback/link-local/site-local/public IP.
Total: 109 → 132 unit tests.
Phase 4 далее: admin-ui /webhooks page (list + create + delivery history).