webhook

package
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 13, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Package webhook delivers a repository's recorded events to its registered hooks. It is the leaf consumer that turns an event into outgoing HTTP: it renders the delivery payload through the presenter, signs it, posts it behind an SSRF guard, and records the result. It may import domain, presenter, store, and worker because nothing in those packages imports it back.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewClient

func NewClient(opts ClientOptions) *http.Client

NewClient builds an HTTP client whose dialer refuses to connect to private, loopback, link-local, or unspecified addresses unless an Allow prefix covers them. The guard runs after DNS resolution on the address the dialer is about to connect to, so a public hostname that resolves to a private IP is still blocked, the rebinding case a URL-only check misses.

func Verify

func Verify(secret, sig string, body []byte) bool

Verify reports whether sig is the valid X-Hub-Signature-256 value for body under secret. The comparison is constant time so a caller cannot learn the secret by timing rejections. An empty secret or a missing signature never verifies.

Types

type ClientOptions

type ClientOptions struct {
	Timeout time.Duration
	Allow   []netip.Prefix
}

ClientOptions configures the delivery HTTP client. Timeout bounds a single POST. Allow lists CIDR ranges that are permitted even though they would otherwise be blocked as private or loopback, the escape hatch an operator uses to deliver to an internal receiver on a trusted network (and the test harness uses to reach its loopback listener). InsecureSkipVerify mirrors a hook's insecure_ssl flag at the transport level.

type Deliverer

type Deliverer struct {
	// contains filtered or unexported fields
}

Deliverer holds the wiring the two webhook job handlers share: the store, the renderer, the guarded HTTP client, the enqueuer the fan-out submits delivery jobs through, and the version string the User-Agent carries.

func NewDeliverer

func NewDeliverer(st Store, r *Renderer, client *http.Client, enq worker.Enqueuer, version string) *Deliverer

NewDeliverer wires a Deliverer. A nil client uses a default guarded client that blocks private and loopback destinations.

func (*Deliverer) DeliverEventHandler

func (d *Deliverer) DeliverEventHandler() worker.Handler

DeliverEventHandler binds the deliver_event kind: it loads the recorded event, renders its payload, stores the rendered feed payload back on the event, and enqueues one deliver_webhook job per active hook subscribed to the event. A missing or unrenderable event is a permanent error; a transient store or enqueue failure returns an error so the queue retries the whole fan-out.

func (*Deliverer) DeliverWebhookHandler

func (d *Deliverer) DeliverWebhookHandler() worker.Handler

DeliverWebhookHandler binds the deliver_webhook kind: it loads the hook, builds the request (rendering the event afresh, or replaying a recorded delivery when the job is a redelivery), POSTs it behind the SSRF guard, records the result, and updates the hook's last response. A non-2xx response returns an error so the queue retries, accepting at-least-once delivery.

type Rendered

type Rendered struct {
	Event        string // X-GitHub-Event, e.g. "push"
	Action       string // empty for push
	RepositoryID int64  // the event's repository database id
	Body         []byte // the webhook POST body
	Payload      []byte // the Events-API payload object stored on the event
}

Rendered is the result of rendering one event: the delivery body, the compact feed payload to store, and the header coordinates a delivery carries.

type Renderer

type Renderer struct {
	// contains filtered or unexported fields
}

Renderer turns a recorded event into the two JSON documents the milestone serves: the body POSTed to a hook and the compact payload the Events API stores on the event row. It lives here, not in domain, because it imports the presenter to reach the exact wire shapes, and domain may not.

func NewRenderer

func NewRenderer(repos repoLoader, issues issueLoader, pulls pullLoader, users userLoader, urls *presenter.URLBuilder, format nodeid.Format) *Renderer

NewRenderer wires a Renderer over the domain loaders and the presenter.

func (*Renderer) BindGit added in v0.1.3

func (r *Renderer) BindGit(g gitLoader)

BindGit attaches the git layer the push renderer walks the pushed range through. Without it a push body has empty commits and a null head_commit.

func (*Renderer) BindReleases added in v0.1.3

func (r *Renderer) BindReleases(rl releaseLoader)

BindReleases attaches the loader a release body renders its release through.

func (*Renderer) BindReviews added in v0.1.3

func (r *Renderer) BindReviews(rl reviewLoader)

BindReviews attaches the review loader the pull_request_review and pull_request_review_comment bodies render their review and comment through.

func (*Renderer) Render

Render assembles an event's objects and renders both documents. push carries the moved refs for a push; cd carries ref detail for create/delete events; detail carries the secondary coordinates some bodies embed, like the moved head shas a pull_request synchronize reports. Each is nil when the event type has no use for it.

func (*Renderer) RenderPing added in v0.1.3

func (r *Renderer) RenderPing(ctx context.Context, row *store.WebhookRow, senderPK int64) (*Rendered, error)

RenderPing builds the body of a ping delivery: {zen, hook_id, hook} plus the repository and sender every delivery carries. A ping has no event row, so it renders straight from the hook.

type Signatures

type Signatures struct {
	SHA256 string // value of X-Hub-Signature-256, e.g. "sha256=<hex>"
	SHA1   string // value of X-Hub-Signature, e.g. "sha1=<hex>" (legacy)
}

Signatures holds the headers a webhook POST carries to prove the body was sent by a hook that knows the shared secret. Both are present when a secret is set; both are empty when it is not, and the delivery sends neither header.

func Sign

func Sign(secret string, body []byte) Signatures

Sign computes the HMAC signatures over the exact request body using the hook's secret. The body must be the literal bytes that go on the wire: for a form-encoded delivery that is "payload=<urlencoded json>", not the bare JSON, so a receiver recomputing the digest over what it received matches. An empty secret yields empty signatures.

type Store

type Store interface {
	GetEventByPK(ctx context.Context, pk int64) (*store.EventRow, error)
	SetEventPayload(ctx context.Context, pk int64, payload string) error
	ListActiveWebhooks(ctx context.Context, repoPK int64) ([]store.WebhookRow, error)
	RepoByPK(ctx context.Context, pk int64) (*store.RepoRow, error)
	UserByPK(ctx context.Context, pk int64) (*store.UserRow, error)
	RepoByOwnerName(ctx context.Context, owner, name string) (*store.RepoRow, error)
	GetWebhookByPK(ctx context.Context, pk int64) (*store.WebhookRow, error)
	SetWebhookLastResponse(ctx context.Context, pk int64, summary string) error
	InsertDelivery(ctx context.Context, d *store.WebhookDeliveryRow) error
	GetDeliveryByPK(ctx context.Context, pk int64) (*store.WebhookDeliveryRow, error)
}

Store is the slice of the metadata store the delivery worker drives: the event it renders, the hooks it fans out to, and the delivery history it records. The concrete store satisfies it directly.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL