Documentation
¶
Index ¶
- Constants
- Variables
- func PrincipalDeviceContextMiddleware(apiKeyAuthenticator APIKeyAuthenticator) func(http.Handler) http.Handler
- func WithPrincipal(ctx context.Context, principal Principal) context.Context
- type APIKeyAuthenticator
- type Address
- type AddressEvent
- type AddressEventBucket
- type AddressHistory
- type AddressHistoryQuery
- type AddressID
- type AddressObserver
- type AddressStateChange
- type CreateAddressParams
- type CreateDeviceParams
- type Device
- type DeviceID
- type EventSource
- type EventType
- type HTTPHandler
- func (h *HTTPHandler) APIKeyAuthenticator() APIKeyAuthenticator
- func (h *HTTPHandler) AddAddress(ctx context.Context, request httpapi.AddAddressRequestObject) (httpapi.AddAddressResponseObject, error)
- func (h *HTTPHandler) CreateDevice(ctx context.Context, request httpapi.CreateDeviceRequestObject) (httpapi.CreateDeviceResponseObject, error)
- func (h *HTTPHandler) DeleteDevice(ctx context.Context, request httpapi.DeleteDeviceRequestObject) (httpapi.DeleteDeviceResponseObject, error)
- func (h *HTTPHandler) DeviceHeartbeat(ctx context.Context, request httpapi.DeviceHeartbeatRequestObject) (httpapi.DeviceHeartbeatResponseObject, error)
- func (h *HTTPHandler) DeviceHeartbeatByAPIKey(ctx context.Context, request httpapi.DeviceHeartbeatByAPIKeyRequestObject) (httpapi.DeviceHeartbeatByAPIKeyResponseObject, error)
- func (h *HTTPHandler) DisableAddress(ctx context.Context, request httpapi.DisableAddressRequestObject) (httpapi.DisableAddressResponseObject, error)
- func (h *HTTPHandler) GetAddressHistory(ctx context.Context, request httpapi.GetAddressHistoryRequestObject) (httpapi.GetAddressHistoryResponseObject, error)
- func (h *HTTPHandler) GetDevice(ctx context.Context, request httpapi.GetDeviceRequestObject) (httpapi.GetDeviceResponseObject, error)
- func (h *HTTPHandler) RegenerateDeviceAPIKey(ctx context.Context, request httpapi.RegenerateDeviceAPIKeyRequestObject) (httpapi.RegenerateDeviceAPIKeyResponseObject, error)
- type IPEntry
- type Principal
- type Repository
- func (r *Repository) CheckAddressOwnership(ctx context.Context, deviceID DeviceID, addressID AddressID) error
- func (r *Repository) CreateAddress(ctx context.Context, params CreateAddressParams, source EventSource) (*Address, error)
- func (r *Repository) CreateDevice(ctx context.Context, params CreateDeviceParams) (*Device, error)
- func (r *Repository) DeleteDevice(ctx context.Context, deviceID DeviceID) error
- func (r *Repository) DisableAddress(ctx context.Context, addressID AddressID) (*Address, error)
- func (r *Repository) DisableAddresses(ctx context.Context, addressIDs []AddressID, source EventSource) ([]Address, error)
- func (r *Repository) EnableAddress(ctx context.Context, addressID AddressID, source EventSource) (*Address, error)
- func (r *Repository) GetAddress(ctx context.Context, addressID AddressID) (*Address, error)
- func (r *Repository) GetAddressForDeviceByIP(ctx context.Context, deviceID DeviceID, ip netip.Addr) (*Address, error)
- func (r *Repository) GetAddressHistory(ctx context.Context, q AddressHistoryQuery) (AddressHistory, error)
- func (r *Repository) GetDevice(ctx context.Context, id DeviceID) (*Device, error)
- func (r *Repository) GetDeviceByAPIKeyHash(ctx context.Context, keyHash string) (*Device, error)
- func (r *Repository) GetEnabledIPEntries(ctx context.Context) ([]IPEntry, error)
- func (r *Repository) RefreshAddress(ctx context.Context, addressID AddressID, source EventSource) (*Address, error)
- func (r *Repository) RunInTx(ctx context.Context, fn func(repository) error) error
- func (r *Repository) UpdateAPIKey(ctx context.Context, deviceID DeviceID, keyHash string, keyPrefix string) error
- type Service
- func (s *Service) AddAddressObserver(o AddressObserver)
- func (s *Service) Authenticate(ctx context.Context, rawKey string) (*Principal, error)
- func (s *Service) CreateDevice(ctx context.Context, name string) (*Device, string, error)
- func (s *Service) DeleteDevice(ctx context.Context, deviceID DeviceID) error
- func (s *Service) DisableAddress(ctx context.Context, deviceID DeviceID, addressID AddressID) (*Address, error)
- func (s *Service) DisableAddresses(ctx context.Context, addressIDs []AddressID, source EventSource) error
- func (s *Service) GetAddressHistory(ctx context.Context, query AddressHistoryQuery) (AddressHistory, error)
- func (s *Service) GetDevice(ctx context.Context, deviceID DeviceID) (*Device, error)
- func (s *Service) GetEnabledIPEntries(ctx context.Context) ([]IPEntry, error)
- func (s *Service) RegenerateAPIKey(ctx context.Context, deviceID DeviceID) (*Device, string, error)
- func (s *Service) RegisterAddressActivity(ctx context.Context, deviceID DeviceID, inputIP string, source EventSource) (*Address, EventType, error)
Constants ¶
const ( AttrKeyError = logging.AttrKeyError AttrKeyAddressID = "address_id" AttrKeyAddressIP = "address_ip" AttrKeyClientIP = "client_ip" AttrKeyCount = "count" AttrKeyDeviceID = "device_id" AttrKeyDeviceName = "device_name" AttrKeyAddressEventType = "address_event_type" )
Slog attribute key names for the device domain. Use these constants when logging so keys are consistent and typo-safe across handlers and services.
const APIKeyPrefix = "wdk_"
Variables ¶
var ( ErrDeviceNotFound = errors.New("device not found") ErrDuplicateDeviceName = errors.New("device name already in use") ErrAddressNotFound = errors.New("device address not found") ErrInvalidIPFormat = errors.New("invalid IP address format") ErrInvalidDeviceIP = errors.New("ip address is not valid for device registration") ErrTrustedProxyIPRejected = errors.New("ip address belongs to trusted proxy") ErrAddressNotOwnedByDevice = errors.New("address is not owned by the device") ErrInvalidAPIKey = errors.New("invalid api key") )
Functions ¶
func PrincipalDeviceContextMiddleware ¶
func PrincipalDeviceContextMiddleware(apiKeyAuthenticator APIKeyAuthenticator) func(http.Handler) http.Handler
PrincipalDeviceContextMiddleware resolves the api key into a Device Principal and injects it into the context.
Types ¶
type APIKeyAuthenticator ¶
type APIKeyAuthenticator interface {
Authenticate(ctx context.Context, rawKey string) (*Principal, error)
}
APIKeyAuthenticator defines the interface for authenticating device API keys.
type Address ¶
type Address struct {
ID AddressID `db:"id"`
DeviceID DeviceID `db:"device_id"`
IP string `db:"ip"`
IsEnabled bool `db:"is_enabled"`
Source EventSource `db:"source"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
Address represents an address row with its current enabled/disabled state and metadata.
type AddressEvent ¶
type AddressEvent struct {
Type EventType
AddressID AddressID
DeviceID DeviceID
OccurredAt time.Time
}
func NewAddressEvent ¶
func NewAddressEvent(address *Address, eventType EventType) AddressEvent
func (AddressEvent) IsAddressEnabled ¶
func (e AddressEvent) IsAddressEnabled() bool
IsAddressEnabled returns true for Created, Enabled, and Refreshed events (address is enabled); false for Disabled.
func (AddressEvent) LogValue ¶
func (e AddressEvent) LogValue() slog.Value
type AddressEventBucket ¶
type AddressEventBucket struct {
Timestamp database.DBTime `db:"bucket"`
ActiveCount int `db:"active_count"`
EventCount int `db:"event_count"`
}
AddressEventBucket holds aggregated activity data for one time period. Timestamp uses DBTime because SQLite's strftime returns TEXT even for DATETIME columns, and DBTime handles the multi-format scanning.
type AddressHistory ¶
type AddressHistory struct {
Buckets []AddressEventBucket
Events []AddressStateChange
TotalEvents int
QueryLimit int // effective limit used for the query, needed for cursor logic
}
AddressHistory holds the complete history response.
type AddressHistoryQuery ¶
type AddressHistoryQuery struct {
From time.Time
To time.Time
Granularity timebucket.Granularity
DeviceIDs []DeviceID // empty = all devices
Source *string
IsEnabled *bool
IP *string
BeforeID *int64 // cursor for events pagination
Limit int // events limit (default 50, max 200)
IncludeAll bool // when false (default), only state-change events are returned
}
AddressHistoryQuery encapsulates all filters and pagination for history queries.
func (*AddressHistoryQuery) Validate ¶
func (q *AddressHistoryQuery) Validate() error
Validate normalizes defaults and validates business rules on the query. Must be called before passing the query to the repository.
type AddressObserver ¶
type AddressObserver interface {
OnAddressEvent(ctx context.Context, event AddressEvent)
}
type AddressStateChange ¶
type AddressStateChange struct {
ID int64 `db:"id"`
CreatedAt time.Time `db:"created_at"`
IP string `db:"ip"`
IsEnabled bool `db:"is_enabled"`
Source EventSource `db:"source"`
DeviceID DeviceID `db:"device_id"`
DeviceName string `db:"device_name"`
}
AddressStateChange represents a single recorded state change from the address_events audit table.
type CreateAddressParams ¶
CreateAddressParams holds only what is necessary to create an address.
func NewCreateAddressParams ¶
type CreateDeviceParams ¶
func NewCreateDeviceParams ¶
func NewCreateDeviceParams(name string) (CreateDeviceParams, string, error)
type EventSource ¶
type EventSource string
const ( EventSourceHeartbeat EventSource = "heartbeat" EventSourceManual EventSource = "manual" EventSourceExpiry EventSource = "expiry" )
type HTTPHandler ¶
type HTTPHandler struct {
// contains filtered or unexported fields
}
func NewHTTPHandler ¶
func NewHTTPHandler(service *Service, logger *slog.Logger) *HTTPHandler
func (*HTTPHandler) APIKeyAuthenticator ¶
func (h *HTTPHandler) APIKeyAuthenticator() APIKeyAuthenticator
func (*HTTPHandler) AddAddress ¶
func (h *HTTPHandler) AddAddress(ctx context.Context, request httpapi.AddAddressRequestObject) (httpapi.AddAddressResponseObject, error)
func (*HTTPHandler) CreateDevice ¶
func (h *HTTPHandler) CreateDevice(ctx context.Context, request httpapi.CreateDeviceRequestObject) (httpapi.CreateDeviceResponseObject, error)
func (*HTTPHandler) DeleteDevice ¶
func (h *HTTPHandler) DeleteDevice(ctx context.Context, request httpapi.DeleteDeviceRequestObject) (httpapi.DeleteDeviceResponseObject, error)
func (*HTTPHandler) DeviceHeartbeat ¶
func (h *HTTPHandler) DeviceHeartbeat(ctx context.Context, request httpapi.DeviceHeartbeatRequestObject) (httpapi.DeviceHeartbeatResponseObject, error)
func (*HTTPHandler) DeviceHeartbeatByAPIKey ¶
func (h *HTTPHandler) DeviceHeartbeatByAPIKey(ctx context.Context, request httpapi.DeviceHeartbeatByAPIKeyRequestObject) (httpapi.DeviceHeartbeatByAPIKeyResponseObject, error)
func (*HTTPHandler) DisableAddress ¶
func (h *HTTPHandler) DisableAddress(ctx context.Context, request httpapi.DisableAddressRequestObject) (httpapi.DisableAddressResponseObject, error)
func (*HTTPHandler) GetAddressHistory ¶
func (h *HTTPHandler) GetAddressHistory(ctx context.Context, request httpapi.GetAddressHistoryRequestObject) (httpapi.GetAddressHistoryResponseObject, error)
func (*HTTPHandler) GetDevice ¶
func (h *HTTPHandler) GetDevice(ctx context.Context, request httpapi.GetDeviceRequestObject) (httpapi.GetDeviceResponseObject, error)
func (*HTTPHandler) RegenerateDeviceAPIKey ¶
func (h *HTTPHandler) RegenerateDeviceAPIKey(ctx context.Context, request httpapi.RegenerateDeviceAPIKeyRequestObject) (httpapi.RegenerateDeviceAPIKeyResponseObject, error)
type IPEntry ¶
type IPEntry struct {
IP string `db:"ip"`
DeviceID DeviceID `db:"device_id"`
AddressID AddressID `db:"address_id"`
}
IPEntry associates an enabled IP address with the device and address that last registered it. When multiple devices share the same IP, the most recently active device wins.
type Principal ¶
type Principal struct {
DeviceID DeviceID
}
func NewPrincipal ¶
func PrincipalFromDevice ¶
type Repository ¶
type Repository struct {
// contains filtered or unexported fields
}
func NewRepository ¶
func NewRepository(db *sqlx.DB) *Repository
func (*Repository) CheckAddressOwnership ¶
func (*Repository) CreateAddress ¶
func (r *Repository) CreateAddress(ctx context.Context, params CreateAddressParams, source EventSource) (*Address, error)
func (*Repository) CreateDevice ¶
func (r *Repository) CreateDevice(ctx context.Context, params CreateDeviceParams) (*Device, error)
func (*Repository) DeleteDevice ¶
func (r *Repository) DeleteDevice(ctx context.Context, deviceID DeviceID) error
func (*Repository) DisableAddress ¶
func (*Repository) DisableAddresses ¶
func (r *Repository) DisableAddresses(ctx context.Context, addressIDs []AddressID, source EventSource) ([]Address, error)
func (*Repository) EnableAddress ¶
func (r *Repository) EnableAddress(ctx context.Context, addressID AddressID, source EventSource) (*Address, error)
func (*Repository) GetAddress ¶
GetAddress returns the current state for a single address ID.
func (*Repository) GetAddressForDeviceByIP ¶
func (*Repository) GetAddressHistory ¶
func (r *Repository) GetAddressHistory(ctx context.Context, q AddressHistoryQuery) (AddressHistory, error)
func (*Repository) GetDeviceByAPIKeyHash ¶
func (*Repository) GetEnabledIPEntries ¶
func (r *Repository) GetEnabledIPEntries(ctx context.Context) ([]IPEntry, error)
func (*Repository) RefreshAddress ¶
func (r *Repository) RefreshAddress(ctx context.Context, addressID AddressID, source EventSource) (*Address, error)
RefreshAddress records activity for an already-enabled address
func (*Repository) RunInTx ¶
func (r *Repository) RunInTx(ctx context.Context, fn func(repository) error) error
RunInTx runs the callback function inside a transaction. If already running in a transaction context, do not create a new one and reuse it
func (*Repository) UpdateAPIKey ¶
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
func NewService ¶
func (*Service) AddAddressObserver ¶
func (s *Service) AddAddressObserver(o AddressObserver)
func (*Service) Authenticate ¶
func (*Service) CreateDevice ¶
func (*Service) DeleteDevice ¶
func (*Service) DisableAddress ¶
func (*Service) DisableAddresses ¶
func (*Service) GetAddressHistory ¶
func (s *Service) GetAddressHistory(ctx context.Context, query AddressHistoryQuery) (AddressHistory, error)