feat(webhook,hibp): add dunite-webhook and dunite-hibp leaf crates #37

Merged
nrupard merged 1 commit from feat/DEV-527-webhook-hibp-crates into main 2026-08-06 16:29:32 +02:00
Owner

What

Adds two leaf crates (DEV-527, under BUNYIP-340), per the leaf-crate topology already used for geoip / ipenrich / image-upload: keep reqwest and the crypto out of dunite-core's foundation, and let a consumer adopt one without moving its dunite-core rev.

  • dunite-webhook - outbound HMAC-SHA256 signed webhook sender. WebhookSender::new(secret), sign(), send(url, payload) (fire-and-forget: logs, never errors), dispatch_with_retries(url, body, max_attempts) (signs once, exponential backoff, returns the last error on exhaustion so the caller can persist a replayable failure). The payload shape and endpoint are the caller's.
  • dunite-hibp - HaveIBeenPwned k-anonymity password-breach check. check_password_breach(), is_breached(), BreachCheckOutcome. In-process SHA-1, only the 5-char prefix leaves the process, fail-open on outage.

Both are the bunyip implementations made framework-neutral (no product strings). geoip was the third "stateless service" in DEV-527's spec and already shipped as dunite-geoip (DEV-531), so this issue's remaining scope is breach + webhook.

Deps

Adds sha1 = "0.10" to [workspace.dependencies] (the HIBP range API requires SHA-1). reqwest / hmac / sha2 / hex / wiremock were already workspace deps.

Validation

cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace green in the pinned rust-builder image. Tests: HMAC determinism + per-payload / per-secret distinctness, plus wiremock deliver-with-valid-signature and retry-then-exhaust for the webhook; SHA-1 hash pinning for HIBP.

Consumer follow-up (bunyip, after merge)

  • crates/bunyip-domain/src/services/password_breach.rs -> a re-export shim over dunite_hibp (the geoip pattern).
  • crates/bunyip-domain/src/services/webhook.rs -> keep the WebhookService wrapper (it builds the event payloads from bunyip's Application) over a dunite_webhook::WebhookSender.

Pinned as leaves, so consuming them does not move bunyip's dunite-core rev.

#DEV-527

## What Adds two leaf crates (DEV-527, under BUNYIP-340), per the leaf-crate topology already used for geoip / ipenrich / image-upload: keep `reqwest` and the crypto out of `dunite-core`'s foundation, and let a consumer adopt one without moving its `dunite-core` rev. - **`dunite-webhook`** - outbound HMAC-SHA256 signed webhook sender. `WebhookSender::new(secret)`, `sign()`, `send(url, payload)` (fire-and-forget: logs, never errors), `dispatch_with_retries(url, body, max_attempts)` (signs once, exponential backoff, returns the last error on exhaustion so the caller can persist a replayable failure). The payload shape and endpoint are the caller's. - **`dunite-hibp`** - HaveIBeenPwned k-anonymity password-breach check. `check_password_breach()`, `is_breached()`, `BreachCheckOutcome`. In-process SHA-1, only the 5-char prefix leaves the process, fail-open on outage. Both are the bunyip implementations made framework-neutral (no product strings). geoip was the third "stateless service" in DEV-527's spec and already shipped as `dunite-geoip` (DEV-531), so this issue's remaining scope is breach + webhook. ## Deps Adds `sha1 = "0.10"` to `[workspace.dependencies]` (the HIBP range API requires SHA-1). `reqwest` / `hmac` / `sha2` / `hex` / `wiremock` were already workspace deps. ## Validation `cargo fmt --all --check`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test --workspace` green in the pinned rust-builder image. Tests: HMAC determinism + per-payload / per-secret distinctness, plus wiremock deliver-with-valid-signature and retry-then-exhaust for the webhook; SHA-1 hash pinning for HIBP. ## Consumer follow-up (bunyip, after merge) - `crates/bunyip-domain/src/services/password_breach.rs` -> a re-export shim over `dunite_hibp` (the geoip pattern). - `crates/bunyip-domain/src/services/webhook.rs` -> keep the `WebhookService` wrapper (it builds the event payloads from bunyip's `Application`) over a `dunite_webhook::WebhookSender`. Pinned as leaves, so consuming them does not move bunyip's `dunite-core` rev. #DEV-527
feat(webhook,hibp): add dunite-webhook and dunite-hibp leaf crates
All checks were successful
Check / fmt + clippy + test (pull_request) Successful in 47s
create-release / create-release (pull_request) Has been skipped
08b555582f
Extracts two stateless HTTP helpers from bunyip into leaf crates (DEV-527), matching the geoip/ipenrich/image-upload topology so reqwest and the crypto stay out of dunite-core and a consumer can adopt one without moving its dunite-core rev.

dunite-webhook: outbound HMAC-SHA256 signed sender. WebhookSender::new/sign/send (fire-and-forget) and dispatch_with_retries (signs once, exponential backoff, returns the last error on exhaustion). The payload shape and endpoint are the caller's.

dunite-hibp: HaveIBeenPwned k-anonymity password-breach check (check_password_breach / is_breached / BreachCheckOutcome). In-process SHA-1, only the 5-char prefix leaves the process, fail-open on outage.

Adds sha1 = "0.10" to the workspace (HIBP requires SHA-1); reqwest/hmac/sha2/hex/wiremock were already present. CLAUDE.md crates block and dependency-direction note updated.

#DEV-527
nrupard deleted branch feat/DEV-527-webhook-hibp-crates 2026-08-06 16:29:32 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/dunite!37
No description provided.