Documentation
¶
Overview ¶
Package notify implements notification channels for grant request lifecycle events. Today this is Slack-only. Notifications are posted outbound to a configured channel and, when an inbound transport is configured, carry interactive Approve / Deny buttons whose clicks flow back into the decision pipeline. Two inbound transports are supported: a signing-secret-verified HTTPS endpoint (see internal/api/slack_interactions.go) and an outbound Socket Mode connection for deployments that can't accept inbound Slack traffic (see internal/api/slack_socketmode.go). With neither configured the feature degrades to link-through-UI: messages have no buttons and the admin decides in the dbbat UI.
Index ¶
Constants ¶
const ( ActionApprove = "grant_request_approve" ActionDeny = "grant_request_deny" )
Slack interaction action IDs carried by the Approve / Deny buttons. The inbound interaction handler matches on these; kept here so the button rendering and the handler can't drift.
Variables ¶
var ErrAppTokenWithoutBotToken = errors.New("DBB_SLACK_NOTIFY_APP_TOKEN set without DBB_SLACK_NOTIFY_BOT_TOKEN")
ErrAppTokenWithoutBotToken is returned by NewSlackNotifier when an app-level token (Socket Mode) is set but no bot token is: Socket Mode receives interactions that ride on notification messages and posts decisions with the bot token, so it is meaningless without notifications.
var ErrChannelMissing = errors.New("DBB_SLACK_NOTIFY_BOT_TOKEN set without DBB_SLACK_NOTIFY_CHANNEL")
ErrChannelMissing is returned by NewSlackNotifier when a bot token is set but no channel is configured.
var ErrPublicURLMissing = errors.New("DBB_SLACK_NOTIFY_BOT_TOKEN set without DBB_PUBLIC_URL")
ErrPublicURLMissing is returned by NewSlackNotifier when a bot token is set but no public URL is configured.
var ErrSigningSecretWithoutBotToken = errors.New("DBB_SLACK_SIGNING_SECRET set without DBB_SLACK_NOTIFY_BOT_TOKEN")
ErrSigningSecretWithoutBotToken is returned by NewSlackNotifier when a signing secret is set but no bot token is: interactivity lives on notification messages, so it is meaningless without notifications.
Functions ¶
This section is empty.
Types ¶
type GrantAction ¶
type GrantAction string
GrantAction names the lifecycle event a notification refers to.
const ( GrantActionCreated GrantAction = "created" GrantActionApproved GrantAction = "approved" GrantActionDenied GrantAction = "denied" GrantActionCancelled GrantAction = "cancelled" //nolint:misspell // matches DB lifecycle value )
Lifecycle actions a notification can describe. The cancel value matches the DB CHECK constraint spelling.
type GrantRequestEvent ¶
type GrantRequestEvent struct {
Action GrantAction
Request *store.GrantRequest
Definition *store.GrantDefinition
Server *store.Server
Requester *store.User
// Decider is set when Action is approved/denied/canceled.
Decider *store.User
// RequesterSlackID is the requester's linked Slack user ID, or "" if
// the requester has no linked Slack identity. Used to @-mention them.
RequesterSlackID string
// AdminSlackIDs are the Slack user IDs of admins with a linked Slack
// identity, used to @-mention them on the pending message.
AdminSlackIDs []string
// DeciderSlackID is the decider's linked Slack user ID (set on decision
// events), used to @-mention them in the thread reply.
DeciderSlackID string
// Interactive requests Approve / Deny buttons on the pending message.
// Only honored for GrantActionCreated; every other render (decision,
// cancel) rebuilds blocks without buttons so stale buttons disappear.
Interactive bool
}
GrantRequestEvent carries the data the notifier needs to render a message. Fields besides Request are looked up by the API handler before firing — denormalizing here keeps the notifier free of store lookups (so a slow DB doesn't block Slack and vice-versa).
type SlackNotifier ¶
type SlackNotifier struct {
// contains filtered or unexported fields
}
SlackNotifier posts Block Kit messages to a configured channel for grant request lifecycle events. A nil notifier is a graceful no-op (returned from NewSlackNotifier when the bot token is unset).
func NewSlackNotifier ¶
func NewSlackNotifier(cfg config.SlackNotifyConfig, publicURL string, persister SlackPersister, log *slog.Logger) (*SlackNotifier, error)
NewSlackNotifier returns a configured notifier or nil when the feature is disabled. A startup error fires only when notification is enabled but dependent fields are missing — silent disable is intentional for deployments that just don't want it.
func (*SlackNotifier) Interactive ¶ added in v0.14.0
func (n *SlackNotifier) Interactive() bool
Interactive reports whether Approve / Deny buttons are rendered and the inbound interaction endpoint should be served. A nil notifier is never interactive.
func (*SlackNotifier) NotifyGrantRequest ¶
func (n *SlackNotifier) NotifyGrantRequest(ctx context.Context, ev GrantRequestEvent)
NotifyGrantRequest posts a fresh message on creation and chat.update's the existing message on subsequent lifecycle events. All errors are logged and swallowed — notifications are best-effort and must never fail the API request that triggered them.
func (*SlackNotifier) PostThreadReply ¶ added in v0.14.0
func (n *SlackNotifier) PostThreadReply(ctx context.Context, channel, ts, text string)
PostThreadReply posts a plain-text reply in the thread of an existing message (channel + ts). It notifies watchers of a decision — unlike a chat.update, which edits silently. Best-effort: errors are logged and swallowed, and a nil notifier or missing coordinates is a no-op.
func (*SlackNotifier) SigningSecret ¶ added in v0.14.0
func (n *SlackNotifier) SigningSecret() string
SigningSecret returns the Slack app signing secret used to verify inbound interaction callbacks. Empty on a nil notifier or when interactivity is disabled.
type SlackPersister ¶
type SlackPersister interface {
SetGrantRequestSlackMessage(ctx context.Context, uid uuid.UUID, channel, ts string) error
}
SlackPersister is the slice of the store API the notifier uses to remember which Slack message corresponds to a request, so it can chat.update on follow-ups.