// Package webhooks: tenant webhook subscriptions and guarded signed delivery. package webhooks import ( "context" "atlas9.dev/c/core" "atlas9.dev/c/core/iam" ) // Endpoint is a tenant's registered webhook subscription: a URL that receives // signed POSTs for the event types it subscribes to. The HMAC signing secret is // held sealed (DekID + SecretEnc) at rest, mirroring sso.Config; the store // carries it opaquely and never decrypts. The plaintext secret is returned to // the tenant once, at creation, and never again. type Endpoint struct { ID core.ID Tenant core.ID Name string URL string EventTypes []string Active bool DekID core.ID SecretEnc []byte } var ( Cap_Webhooks_CreateEndpoint = iam.NewCap("Webhooks_CreateEndpoint") Cap_Webhooks_UpdateEndpoint = iam.NewCap("Webhooks_UpdateEndpoint") Cap_Webhooks_ReadEndpoint = iam.NewCap("Webhooks_ReadEndpoint") Cap_Webhooks_DeleteEndpoint = iam.NewCap("Webhooks_DeleteEndpoint") // Cap_Webhooks_Send is the app's egress boundary for the webhook channel, the // analog of mail.CapMailSend. Deliveries are made system-wide by the worker, // so it is checked system-wide. Granularity can split later (per tenant or // event) without touching callers, because policy lives in the sender. Cap_Webhooks_Send = iam.NewCap("Webhooks_Send") ) // Store persists webhook endpoints. Endpoint methods are tenant-scoped and // capability-checked like any other resource store. type Store interface { CreateEndpoint(ctx context.Context, e *Endpoint) error UpdateEndpoint(ctx context.Context, e *Endpoint) error GetEndpoint(ctx context.Context, tenant core.ID, id core.ID, out *Endpoint) error ListEndpoints(ctx context.Context, tenant core.ID, page core.PageReq, out *core.Page[Endpoint]) error DeleteEndpoint(ctx context.Context, tenant core.ID, id core.ID) error // ListActiveForEvent returns the tenant's active endpoints subscribed to // eventType, for fanout. Guarded by Cap_Webhooks_ReadEndpoint like the other // reads; Fanout.Emit grants it scoped to the tenant, since the emitting // caller is mid-mutation and needn't hold webhook caps. ListActiveForEvent(ctx context.Context, tenant core.ID, eventType string) ([]Endpoint, error) }