whsource

package
v1.136.0 Latest Latest
Warning

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

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

Documentation

Overview

Package whsource defines an inbound webhook source: the administrator-managed record that says who may post to /hooks/{name}, how a request proves it, how the body becomes events, and how long what lands is kept (#1870).

It holds the model, its defaults and validation, and the PostgreSQL store. The receiver, the compactor and the admin API all read a Source through this package; none of them owns its shape.

Index

Constants

View Source
const (
	// AuthHMAC checks a signature over the body, or over timestamp + "." +
	// body, carried in a header.
	AuthHMAC = "hmac"
	// AuthHeaderToken compares a header's value with the secret.
	AuthHeaderToken = "header_token"
	// AuthBasic checks HTTP Basic credentials: a username and the secret as
	// the password.
	AuthBasic = "basic"
	// AuthPathToken takes the secret as an extra path segment,
	// /hooks/{name}/{token}, for a sender that can set nothing but a URL.
	AuthPathToken = "path_token"
)

Auth modes a source authenticates a request with.

View Source
const (
	AlgorithmSHA256 = "sha256"
	AlgorithmSHA1   = "sha1"

	EncodingHex    = "hex"
	EncodingBase64 = "base64"

	// SignedBody signs the raw body.
	SignedBody = "body"
	// SignedTimestampBody signs the timestamp header's value, a ".", and
	// the raw body, which is what makes a replayed request with an old
	// timestamp fail even though its signature once verified.
	SignedTimestampBody = "timestamp.body"
)

HMAC settings a source chooses between.

View Source
const (
	HandshakeNone = "none"
	// HandshakeCloudEvents answers the CloudEvents HTTP webhook
	// abuse-protection OPTIONS request, which a sender that implements it
	// sends before it delivers anything.
	HandshakeCloudEvents = "cloudevents"
)

Handshakes a source can answer.

View Source
const (
	DefaultMaxBodyBytes        int64 = 1 << 20
	DefaultFlushMaxEvents            = 5000
	DefaultFlushMaxInterval          = time.Second
	DefaultFlushMaxBytes       int64 = 8 << 20
	DefaultBufferLimit               = 50000
	DefaultRawRetentionDays          = 7
	DefaultCompactEveryMinutes       = 60
	DefaultCompactedDays             = 400
	DefaultTolerance                 = 5 * time.Minute
)

Defaults a source gets for every setting it leaves at zero.

Variables

View Source
var ErrExists = errors.New("a webhook source with that name already exists")

ErrExists is returned when a source is created under a name already taken.

View Source
var ErrInvalid = errors.New("invalid webhook source")

ErrInvalid wraps every refusal Validate returns, so a surface can answer 400 for any of them and show the sentence as written.

View Source
var ErrNotFound = errors.New("webhook source not found")

ErrNotFound is returned for a name no source is stored under.

Functions

func Refusal

func Refusal(format string, args ...any) error

Refusal is invalid for a caller that refuses a source on grounds this package cannot see, such as a persona the platform does not define.

func TableName

func TableName(source string) string

TableName is the name readers query the source by, in the scratch schema of its connection.

func Validate

func Validate(s Source) error

Validate refuses a source that could not be served as configured. It is called on the source with its defaults applied, so a zero setting has already been replaced and what is checked is what the receiver would use.

Types

type Auth

type Auth struct {
	Mode           string    `json:"mode"`
	Secret         string    `json:"secret,omitempty"`
	PreviousSecret string    `json:"previous_secret,omitempty"`
	PreviousUntil  time.Time `json:"previous_until,omitzero"`

	// HMAC.
	Algorithm        string `json:"algorithm,omitempty"`
	SignatureHeader  string `json:"signature_header,omitempty"`
	Encoding         string `json:"encoding,omitempty"`
	Prefix           string `json:"prefix,omitempty"`
	TimestampHeader  string `json:"timestamp_header,omitempty"`
	ToleranceSeconds int    `json:"tolerance_seconds,omitempty"`
	Signed           string `json:"signed,omitempty"`

	// HeaderToken.
	Header string `json:"header,omitempty"`

	// Basic.
	Username string `json:"username,omitempty"`
}

Auth is how a request to the source proves it came from the sender.

Secret and PreviousSecret are plaintext in memory and encrypted at rest; the admin API never returns either. PreviousSecret is accepted until PreviousUntil, which is how a secret is rotated without a window in which the sender's requests fail.

func (Auth) Rotate

func (a Auth) Rotate(secret string, overlap time.Duration, now time.Time) Auth

Rotate replaces the secret, keeping the old one valid for overlap. An overlap of zero ends the old secret now.

func (Auth) Secrets

func (a Auth) Secrets(now time.Time) []string

Secrets returns the secrets a request may be verified with at now: the current one, and the previous one while its overlap lasts.

func (Auth) Tolerance

func (a Auth) Tolerance() time.Duration

Tolerance is how far a signed timestamp may be from the receiver's clock.

type Config

type Config struct {
	Handshake     string `json:"handshake,omitempty"`
	MaxBodyBytes  int64  `json:"max_body_bytes,omitempty"`
	Split         string `json:"split,omitempty"`
	EventIDPath   string `json:"event_id_path,omitempty"`
	EventTypePath string `json:"event_type_path,omitempty"`
	KeyPath       string `json:"key_path,omitempty"`

	FlushMaxEvents     int   `json:"flush_max_events,omitempty"`
	FlushMaxIntervalMS int64 `json:"flush_max_interval_ms,omitempty"`
	FlushMaxBytes      int64 `json:"flush_max_bytes,omitempty"`
	BufferLimit        int   `json:"buffer_limit,omitempty"`

	// RateLimitPerMinute is zero for no limit. The limit is on the source,
	// not on a client address: a sender's requests arrive from whatever
	// addresses its infrastructure has.
	RateLimitPerMinute int `json:"rate_limit_per_minute,omitempty"`
	RateLimitBurst     int `json:"rate_limit_burst,omitempty"`

	// Persona is the persona whose members see the source's compacted windows
	// in Resources and search. Empty is the administrator persona, which keeps
	// them to administrators. Querying
	// the table is governed by the Trino connection, not by this. It is set
	// when the source is created and does not change: windows already written
	// stay where they were written.
	Persona string `json:"persona,omitempty"`

	// CompactEveryMinutes is the length of the window a source's events are
	// partitioned and compacted by: 60, the default, compacts each hour once
	// it has ended; a shorter window compacts sooner, into more files. It
	// divides an hour evenly, so windows start on the hour and an hour is
	// always a whole number of them.
	CompactEveryMinutes int `json:"compact_every_minutes,omitempty"`

	// RawRetentionDays is how long a raw segment is kept once the window it
	// belongs to is compacted.
	RawRetentionDays int `json:"raw_retention_days,omitempty"`
	// CompactedRetentionDays is how long a compacted window is kept. Nil is
	// the default; zero keeps it forever.
	CompactedRetentionDays *int `json:"compacted_retention_days,omitempty"`
}

Config is everything about a source other than its authentication.

func (Config) CompactedRetention

func (c Config) CompactedRetention() time.Duration

CompactedRetention is how long a compacted window is kept, and zero for forever.

func (Config) FlushInterval

func (c Config) FlushInterval() time.Duration

FlushInterval is the longest an event waits in the buffer before its segment is written.

func (Config) RawRetention

func (c Config) RawRetention() time.Duration

RawRetention is how long a compacted window's raw segments are kept.

func (Config) Window

func (c Config) Window() time.Duration

Window is the length of the window the source's events are partitioned and compacted by.

type Encryptor

type Encryptor interface {
	Encrypt(plaintext string) (string, error)
	Decrypt(ciphertext string) (string, error)
}

Encryptor encrypts and decrypts one secret. fieldcrypt.RestFieldEncryptor satisfies it; a nil one stores secrets as written.

type Source

type Source struct {
	Name    string `json:"name"`
	Enabled bool   `json:"enabled"`
	Auth    Auth   `json:"auth"`
	Config  Config `json:"config"`
	// Connection is the Trino connection whose scratch catalog holds the
	// source's table. That catalog must read the managed-resources store,
	// because both the raw segments and the compacted windows are written
	// there.
	Connection string    `json:"connection"`
	CreatedBy  string    `json:"created_by"`
	CreatedAt  time.Time `json:"created_at"`
	UpdatedAt  time.Time `json:"updated_at"`
}

Source is one inbound webhook source.

func (Source) CompactedTableName

func (s Source) CompactedTableName() string

CompactedTableName is the table over the compacted windows, underneath the view.

func (Source) RawTableName

func (s Source) RawTableName() string

RawTableName is the table over the raw segments, underneath the view.

func (Source) TableName

func (s Source) TableName() string

TableName is the view readers name for this source.

func (Source) WithDefaults

func (s Source) WithDefaults() Source

WithDefaults returns the source with every zero setting replaced by its default.

type Store

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

Store persists sources in webhook_sources.

func NewStore

func NewStore(db *sql.DB, enc Encryptor) *Store

NewStore creates a source store. enc may be nil.

func (*Store) Create

func (s *Store) Create(ctx context.Context, src Source) error

Create inserts a new source, or returns ErrExists.

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, name string) error

Delete removes a source. Its windows, counts and rejections go with it.

func (*Store) Get

func (s *Store) Get(ctx context.Context, name string) (Source, error)

Get returns one source, or ErrNotFound.

func (*Store) List

func (s *Store) List(ctx context.Context) ([]Source, error)

List returns every source in name order, secrets decrypted.

func (*Store) Update

func (s *Store) Update(ctx context.Context, src Source) error

Update replaces a source's settings. The name, the connection and the creator are not changed: the table was created on that connection under a name derived from the source's.

Jump to

Keyboard shortcuts

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