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
- Variables
- func Refusal(format string, args ...any) error
- func TableName(source string) string
- func Validate(s Source) error
- type Auth
- type Config
- type Encryptor
- type Source
- type Store
- func (s *Store) Create(ctx context.Context, src Source) error
- func (s *Store) Delete(ctx context.Context, name string) error
- func (s *Store) Get(ctx context.Context, name string) (Source, error)
- func (s *Store) List(ctx context.Context) ([]Source, error)
- func (s *Store) Update(ctx context.Context, src Source) error
Constants ¶
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.
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.
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.
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 ¶
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.
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.
var ErrNotFound = errors.New("webhook source not found")
ErrNotFound is returned for a name no source is stored under.
Functions ¶
func Refusal ¶
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.
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 ¶
Rotate replaces the secret, keeping the old one valid for overlap. An overlap of zero ends the old secret now.
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 ¶
CompactedRetention is how long a compacted window is kept, and zero for forever.
func (Config) FlushInterval ¶
FlushInterval is the longest an event waits in the buffer before its segment is written.
func (Config) RawRetention ¶
RawRetention is how long a compacted window's raw segments are kept.
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 ¶
CompactedTableName is the table over the compacted windows, underneath the view.
func (Source) RawTableName ¶
RawTableName is the table over the raw segments, underneath the view.
func (Source) WithDefaults ¶
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.