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 DeviceType
- 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) ListDeviceTypes(_ context.Context, _ httpapi.ListDeviceTypesRequestObject) (httpapi.ListDeviceTypesResponseObject, error)
- func (h *HTTPHandler) RegenerateDeviceAPIKey(ctx context.Context, request httpapi.RegenerateDeviceAPIKeyRequestObject) (httpapi.RegenerateDeviceAPIKeyResponseObject, error)
- func (h *HTTPHandler) UpdateDevice(ctx context.Context, request httpapi.UpdateDeviceRequestObject) (httpapi.UpdateDeviceResponseObject, 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) GetEnabledAddressesForDevice(ctx context.Context, deviceID DeviceID) ([]Address, 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
- func (r *Repository) UpdateDevice(ctx context.Context, device *Device) (*Device, 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) GetEnabledAddressesForDevice(ctx context.Context, deviceID DeviceID) ([]Address, 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)
- func (s *Service) UpdateDevice(ctx context.Context, deviceID DeviceID, input UpdateDeviceInput) (*Device, error)
- type UpdateDeviceInput
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") ErrInvalidDeviceType = errors.New("invalid device type") ErrInvalidDeviceName = errors.New("device name must be between 1 and 50 characters") ErrDescriptionTooLong = errors.New("description must be 200 characters or fewer") ErrIconTooLong = errors.New("icon must be 80 characters or fewer") )
var AllowedDeviceTypes = []DeviceType{ DeviceTypeStatic, DeviceTypeMobile, }
AllowedDeviceTypes is the canonical ordered list of valid device types.
var DeviceTypeLabels = map[DeviceType]string{ DeviceTypeStatic: "Static", DeviceTypeMobile: "Mobile", }
DeviceTypeLabels maps each device type to its display label.
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 Device ¶
type Device struct {
ID DeviceID `db:"id"`
Name string `db:"name"`
DeviceType DeviceType `db:"device_type"`
Description *string `db:"description"`
Icon *string `db:"icon"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
DeletedAt *time.Time `db:"deleted_at"`
KeyPrefix string `db:"key_prefix"`
LastSeenAt *database.DBTime `db:"last_seen_at"`
}
func (*Device) Update ¶ added in v0.2.0
func (d *Device) Update(name *string, deviceType *string, description **string, icon **string) error
Update applies patch inputs to the device in-place, validating each field. Fields with a nil pointer are left unchanged. The device is not mutated unless all validations pass.
- name: nil = keep current; non-nil = rename (validated)
- deviceType: nil = keep current; non-nil = set type (validated)
- description: nil = keep current; non-nil ptr to nil = clear; non-nil ptr to value = set
- icon: same semantics as description
type DeviceType ¶ added in v0.2.0
type DeviceType string
DeviceType describes the network behaviour of a device. Keeping it really simple, in the future rules could be created on top of it. Main usage now is for filtering. For example static devices do not need address lease but mobile do, static ones can have different geoip rules...
const ( DeviceTypeStatic DeviceType = "static" DeviceTypeMobile DeviceType = "mobile" )
type EventSource ¶
type EventSource string
const ( EventSourceHeartbeat EventSource = "heartbeat" EventSourceManual EventSource = "manual" EventSourceExpiry EventSource = "expiry" EventSourceLimitExceeded EventSource = "limit_exceeded" )
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) ListDeviceTypes ¶ added in v0.2.0
func (h *HTTPHandler) ListDeviceTypes(_ context.Context, _ httpapi.ListDeviceTypesRequestObject) (httpapi.ListDeviceTypesResponseObject, error)
func (*HTTPHandler) RegenerateDeviceAPIKey ¶
func (h *HTTPHandler) RegenerateDeviceAPIKey(ctx context.Context, request httpapi.RegenerateDeviceAPIKeyRequestObject) (httpapi.RegenerateDeviceAPIKeyResponseObject, error)
func (*HTTPHandler) UpdateDevice ¶ added in v0.2.0
func (h *HTTPHandler) UpdateDevice(ctx context.Context, request httpapi.UpdateDeviceRequestObject) (httpapi.UpdateDeviceResponseObject, 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) GetEnabledAddressesForDevice ¶ added in v0.2.0
func (r *Repository) GetEnabledAddressesForDevice(ctx context.Context, deviceID DeviceID) ([]Address, error)
GetEnabledAddressesForDevice returns all enabled addresses for a device, ordered by updated_at DESC.
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 ¶
func (*Repository) UpdateDevice ¶ added in v0.2.0
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)
func (*Service) GetEnabledAddressesForDevice ¶ added in v0.2.0
func (s *Service) GetEnabledAddressesForDevice(ctx context.Context, deviceID DeviceID) ([]Address, error)
GetEnabledAddressesForDevice returns all enabled addresses for a device, ordered by updated_at DESC.
func (*Service) GetEnabledIPEntries ¶
func (*Service) RegenerateAPIKey ¶
func (*Service) RegisterAddressActivity ¶
func (*Service) UpdateDevice ¶ added in v0.2.0
type UpdateDeviceInput ¶ added in v0.2.0
type UpdateDeviceInput struct {
Name *string
DeviceType *string
Description **string
Icon **string
}
UpdateDeviceInput carries the raw nullable API values for a device profile update. nil pointer = field was absent in the request (leave unchanged). For Description and Icon, **string semantics apply: nil = absent, *nil = clear, *&s = set.