Documentation
¶
Index ¶
- Constants
- Variables
- func Backoff(attempt int, jitter func() float64) time.Duration
- func Create(ctx context.Context, deps ManageDeps, params CreateParams) (webhookdb.Webhook, error)
- func Delete(ctx context.Context, deps ManageDeps, hookID int64) error
- func Deliver(ctx context.Context, deps DeliverDeps, deliveryID int64) error
- func EnqueuePing(ctx context.Context, deps FanoutDeps, hookID int64) error
- func FanoutOnce(ctx context.Context, deps FanoutDeps) (int, error)
- func GenerateSecret() (string, error)
- func IsSSRF(err error) bool
- func MarshalDeliverPayload(deliveryID int64) ([]byte, error)
- func OpenSecret(box *secretbox.Box, ciphertext, nonce []byte) (string, error)
- func Redeliver(ctx context.Context, deps FanoutDeps, originalID int64) (int64, error)
- func SealSecret(box *secretbox.Box, secret string) (ciphertext, nonce []byte, err error)
- func SetActive(ctx context.Context, deps ManageDeps, hookID int64, active bool) error
- func SignSHA256(secret, body []byte) string
- func UnmarshalDeliverPayload(raw json.RawMessage) (int64, error)
- func Update(ctx context.Context, deps ManageDeps, hookID int64, params UpdateParams) error
- func VerifySHA256(secret, body []byte, sig string) bool
- type CreateParams
- type DeliverDeps
- type FanoutDeps
- type ManageDeps
- type SSRFConfig
- type SSRFError
- type UpdateParams
Constants ¶
const ( KindWebhookFanout worker.Kind = "webhook:fanout" KindWebhookDeliver worker.Kind = "webhook:deliver" KindWebhookPurgeOld worker.Kind = "webhook:purge_old" )
Job kinds shipped by S33. Registered by cmd/shithubd/worker.go.
const BackoffBase = 30 * time.Second
BackoffBase is the smallest retry delay; doubles per attempt.
const BackoffMax = 24 * time.Hour
BackoffMax caps the delay so a stale subscriber doesn't disappear for days at a time. The spec calls for 24 hours.
const FanoutBatch = 200
FanoutBatch caps how many events a single tick drains.
const FanoutConsumer = "webhook_fanout"
FanoutConsumer is the consumer name in domain_events_processed. Distinct from notif's so cursors don't collide.
const MaxBodySize = 25 * 1024 * 1024
MaxBodySize caps the request body at 25 MiB per the spec. We don't build payloads anywhere near this, but the cap protects against pathological event shapes.
const ResponseBodyCap = 32 * 1024
ResponseBodyCap bounds the response body we keep in DB so a malicious receiver can't blow up our storage. Above this we set response_truncated=true.
const SecretLength = 32
SecretLength is the byte length of a freshly minted webhook secret. 32 bytes ≈ 256 bits — well above HMAC-SHA256's effective security.
Variables ¶
var ( ErrBadURL = errors.New("webhook: URL must be http or https with a host") ErrBadContentType = errors.New("webhook: content_type must be json or form") ErrBadOwnerKind = errors.New("webhook: owner_kind must be repo or org") ErrBadEvent = errors.New("webhook: event names must be 1–64 lowercase chars") )
CreateErrors surfaces user-friendly validation failures so handlers can render them in-page.
Functions ¶
func Backoff ¶
Backoff returns the delay before retrying delivery `attempt` (1- indexed: the just-failed attempt counts as 1). Applies ±20% jitter so a fleet of webhooks pointed at one outage doesn't synchronize retries. `jitter` returns a value in [0, 1); pass `nil` for no jitter (deterministic schedule, useful in tests).
func Create ¶
func Create(ctx context.Context, deps ManageDeps, params CreateParams) (webhookdb.Webhook, error)
Create persists a new webhook + emits a synthetic ping delivery so the operator sees an immediate round-trip. Returns the created row.
func Delete ¶
func Delete(ctx context.Context, deps ManageDeps, hookID int64) error
Delete removes a webhook + cascades its deliveries (FK ON DELETE CASCADE).
func Deliver ¶
func Deliver(ctx context.Context, deps DeliverDeps, deliveryID int64) error
Deliver handles one delivery row end-to-end: load + decrypt secret, validate URL via SSRF defense, POST, record outcome, schedule retry or mark terminal. Returns nil on success, an error to be logged on transient failure (the row is updated regardless).
func EnqueuePing ¶
func EnqueuePing(ctx context.Context, deps FanoutDeps, hookID int64) error
EnqueuePing creates a synthetic ping delivery and enqueues it. Used on webhook create / re-enable so the operator sees an immediate round-trip.
func FanoutOnce ¶
func FanoutOnce(ctx context.Context, deps FanoutDeps) (int, error)
FanoutOnce drains domain_events past the persisted cursor, finds matching webhooks, and inserts a webhook_deliveries row + enqueues a `webhook:deliver` job per match. Returns the number of events processed; the caller decides to re-enqueue when full.
The consumer cursor is advanced after each event regardless of whether the event matched any subscribers — an event without subscribers is "processed" the same as one with five.
func GenerateSecret ¶
GenerateSecret returns a hex-encoded random secret suitable for dropping into the webhook config form. The hex shape keeps it easy to paste into receiver code.
func MarshalDeliverPayload ¶
MarshalDeliverPayload exposes the json shape so downstream packages (worker registration, tests) build the same payload without duplicating the struct.
func OpenSecret ¶
OpenSecret decrypts the stored ciphertext+nonce. Decryption failure is a hard error: the deliverer disables the webhook with a precise reason rather than silently signing with garbage.
func Redeliver ¶
Redeliver clones a past delivery into a new pending row so the UI's "redeliver" button preserves the audit trail (redeliver_of points at the original).
func SealSecret ¶
SealSecret AEAD-wraps secret under the supplied box. Returns (ciphertext, nonce) for storage in webhooks.secret_ciphertext + webhooks.secret_nonce.
func SignSHA256 ¶
SignSHA256 returns the X-Shithub-Signature-256 header value for the given body and per-webhook secret. The format mirrors GitHub's (`sha256=<hex>`) so existing receiver libraries verify cleanly.
func UnmarshalDeliverPayload ¶
func UnmarshalDeliverPayload(raw json.RawMessage) (int64, error)
UnmarshalDeliverPayload extracts the id from a worker job's raw JSON. Used by the registered handler.
func Update ¶
func Update(ctx context.Context, deps ManageDeps, hookID int64, params UpdateParams) error
Update applies a config change to a webhook. Returns ErrBadURL etc. on validation failure.
func VerifySHA256 ¶
VerifySHA256 returns true when sig matches HMAC-SHA256(secret, body). Uses constant-time compare so the verifier doesn't leak timing info. Provided as the receiver-side helper that test code (and any future inbound webhook surface) can reuse.
Types ¶
type CreateParams ¶
type CreateParams struct {
OwnerKind string // "repo" or "org"
OwnerID int64
URL string
ContentType string // "json" or "form"
Events []string
Secret string
Active bool
SSL bool
ActorUserID int64
}
CreateParams is the create-form input. Empty Secret means "mint one"; a non-empty value comes through verbatim from the operator (matches GitHub's behaviour where the user can paste their own).
type DeliverDeps ¶
type DeliverDeps struct {
Pool *pgxpool.Pool
Logger *slog.Logger
HTTPClient *http.Client
SecretBox *secretbox.Box
SSRF SSRFConfig
// JitterFn is plumbed for tests; nil => math/rand.Float64.
JitterFn func() float64
}
DeliverDeps wires the deliverer.
type FanoutDeps ¶
FanoutDeps wires the fan-out against the runtime.
type ManageDeps ¶
type ManageDeps struct {
Pool *pgxpool.Pool
SecretBox *secretbox.Box
SSRF SSRFConfig
}
ManageDeps wires the create/update/delete orchestrator.
type SSRFConfig ¶
SSRFConfig is the re-exported alias for ssrf.Config.
func DefaultSSRFConfig ¶
func DefaultSSRFConfig() SSRFConfig
DefaultSSRFConfig returns the production defaults.