notify

package
v0.15.0 Latest Latest
Warning

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

Go to latest
Published: Jul 9, 2026 License: AGPL-3.0 Imports: 9 Imported by: 0

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

View Source
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

View Source
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.

View Source
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.

View Source
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.

View Source
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
	Database   *store.Database
	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.

Jump to

Keyboard shortcuts

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