golib/auth/xhubsig/README.md
AJ ONeal 4abac2a0df
feat(auth/xhubsig): X-Hub-Signature HMAC webhook verification + HTTP middleware
Verify X-Hub-Signature-256 (and SHA-1) webhook signatures. Middleware
buffers and re-exposes the body for downstream handlers. Errors honor
Accept header: TSV default (text/plain for browsers), JSON, CSV, or
Markdown — three fields (error, description, hint) with pseudocode hints.
2026-04-13 17:04:45 -06:00

92 lines
2.3 KiB
Markdown

# xhubsig
Verify [X-Hub-Signature-256](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries) HMAC-SHA256 webhook signatures. HTTP middleware included.
```sh
go get github.com/therootcompany/golib/auth/xhubsig
```
## Middleware
Wrap any `http.Handler`. Verified body is buffered and re-readable by the next handler.
```go
x := xhubsig.New(webhookSecret)
mux.Handle("POST /webhook", x.Require(handleWebhook))
```
Require both SHA-256 and SHA-1 (all must pass):
```go
x := xhubsig.New(webhookSecret, xhubsig.SHA256, xhubsig.SHA1)
```
Accept either SHA-256 or SHA-1 (at least one must be present; all present must pass):
```go
x := xhubsig.New(webhookSecret, xhubsig.SHA256, xhubsig.SHA1)
x.AcceptAny = true
```
Raise the body limit (default 256 KiB):
```go
x.Limit = 1 << 20 // 1 MiB
```
## Sign / Verify
Compute a signature (for sending or testing):
```go
sig := xhubsig.Sign(xhubsig.SHA256, secret, body)
// → "sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17"
```
Verify a signature directly:
```go
err := xhubsig.Verify(xhubsig.SHA256, secret, body, r.Header.Get("X-Hub-Signature-256"))
if errors.Is(err, xhubsig.ErrMissingSignature) { ... }
if errors.Is(err, xhubsig.ErrInvalidSignature) { ... }
```
Signature format: `sha256=<hex hmac-sha256 of raw request body>` using the webhook secret as the HMAC key. sha256 is the default algorithm.
## Error responses
Errors honor the `Accept` header; `Content-Type` matches. Default is TSV.
| `Accept` | Format |
|---|---|
| `text/tab-separated-values` | vertical key-value TSV *(default)* |
| `text/html` | `text/plain` TSV *(browser-safe)* |
| `application/json` | JSON object |
| `text/csv` | vertical key-value CSV |
| `text/markdown` | pipe table |
TSV example (`missing_signature`):
```
field value
error missing_signature
description No valid signature header was found.
hint X-Hub-Signature-256 is required. `X-Hub-Signature-256: sha256=hex(hmac_sha256(secret, body))`
```
JSON example:
```json
{
"error": "missing_signature",
"description": "No valid signature header was found.",
"hint": "X-Hub-Signature-256 is required.\n`X-Hub-Signature-256: sha256=hex(hmac_sha256(secret, body))`"
}
```
Error codes: `missing_signature`, `invalid_signature`, `body_too_large`.
## License
CC0-1.0. Public domain.