device

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 1, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Index

Constants

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

View Source
const APIKeyPrefix = "wdk_"

Variables

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

func WithPrincipal

func WithPrincipal(ctx context.Context, principal Principal) context.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 AddressID

type AddressID int64

func (AddressID) Int64

func (id AddressID) Int64() int64

func (AddressID) String

func (id AddressID) String() string

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

type CreateAddressParams struct {
	DeviceID DeviceID
	IP       netip.Addr
}

CreateAddressParams holds only what is necessary to create an address.

func NewCreateAddressParams

func NewCreateAddressParams(deviceID DeviceID, ipAddress string, trustedProxy netip.Addr) (CreateAddressParams, error)

type CreateDeviceParams

type CreateDeviceParams struct {
	Name      string
	KeyPrefix string
	KeyHash   string
}

func NewCreateDeviceParams

func NewCreateDeviceParams(name string) (CreateDeviceParams, string, error)

type Device

type Device struct {
	ID         DeviceID         `db:"id" `
	Name       string           `db:"name" `
	CreatedAt  time.Time        `db:"created_at" `
	DeletedAt  *time.Time       `db:"deleted_at" `
	KeyPrefix  string           `db:"key_prefix"`
	LastSeenAt *database.DBTime `db:"last_seen_at"`
}

type DeviceID

type DeviceID int64

func (DeviceID) Int64

func (id DeviceID) Int64() int64

func (DeviceID) String

func (id DeviceID) String() string

type EventSource

type EventSource string
const (
	EventSourceHeartbeat EventSource = "heartbeat"
	EventSourceManual    EventSource = "manual"
	EventSourceExpiry    EventSource = "expiry"
)

type EventType

type EventType string
const (
	EventTypeAddressCreated   EventType = "address_created"
	EventTypeAddressEnabled   EventType = "address_enabled"
	EventTypeAddressRefreshed EventType = "address_refreshed"
	EventTypeAddressDisabled  EventType = "address_disabled"
)

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 (*HTTPHandler) GetDevice

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 NewPrincipal(deviceID DeviceID) *Principal

func PrincipalFromContext

func PrincipalFromContext(ctx context.Context) (*Principal, bool)

func PrincipalFromDevice

func PrincipalFromDevice(device *Device) *Principal

type Repository

type Repository struct {
	// contains filtered or unexported fields
}

func NewRepository

func NewRepository(db *sqlx.DB) *Repository

func (*Repository) CheckAddressOwnership

func (r *Repository) CheckAddressOwnership(ctx context.Context, deviceID DeviceID, addressID AddressID) error

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 (r *Repository) DisableAddress(ctx context.Context, addressID AddressID) (*Address, error)

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

func (r *Repository) GetAddress(ctx context.Context, addressID AddressID) (*Address, error)

GetAddress returns the current state for a single address ID.

func (*Repository) GetAddressForDeviceByIP

func (r *Repository) GetAddressForDeviceByIP(ctx context.Context, deviceID DeviceID, ip netip.Addr) (*Address, error)

func (*Repository) GetAddressHistory

func (r *Repository) GetAddressHistory(ctx context.Context, q AddressHistoryQuery) (AddressHistory, error)

func (*Repository) GetDevice

func (r *Repository) GetDevice(ctx context.Context, id DeviceID) (*Device, error)

func (*Repository) GetDeviceByAPIKeyHash

func (r *Repository) GetDeviceByAPIKeyHash(ctx context.Context, keyHash string) (*Device, error)

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 (r *Repository) UpdateAPIKey(ctx context.Context, deviceID DeviceID, keyHash string, keyPrefix string) error

type Service

type Service struct {
	// contains filtered or unexported fields
}

func NewService

func NewService(repo repository, logger *slog.Logger, trustedProxy netip.Addr) *Service

func (*Service) AddAddressObserver

func (s *Service) AddAddressObserver(o AddressObserver)

func (*Service) Authenticate

func (s *Service) Authenticate(ctx context.Context, rawKey string) (*Principal, error)

func (*Service) CreateDevice

func (s *Service) CreateDevice(ctx context.Context, name string) (*Device, string, error)

func (*Service) DeleteDevice

func (s *Service) DeleteDevice(ctx context.Context, deviceID DeviceID) error

func (*Service) DisableAddress

func (s *Service) DisableAddress(ctx context.Context, deviceID DeviceID, addressID AddressID) (*Address, error)

func (*Service) DisableAddresses

func (s *Service) DisableAddresses(ctx context.Context, addressIDs []AddressID, source EventSource) error

func (*Service) GetAddressHistory

func (s *Service) GetAddressHistory(ctx context.Context, query AddressHistoryQuery) (AddressHistory, error)

func (*Service) GetDevice

func (s *Service) GetDevice(ctx context.Context, deviceID DeviceID) (*Device, error)

func (*Service) GetEnabledIPEntries

func (s *Service) GetEnabledIPEntries(ctx context.Context) ([]IPEntry, error)

func (*Service) RegenerateAPIKey

func (s *Service) RegenerateAPIKey(ctx context.Context, deviceID DeviceID) (*Device, string, error)

func (*Service) RegisterAddressActivity

func (s *Service) RegisterAddressActivity(ctx context.Context, deviceID DeviceID, inputIP string, source EventSource) (*Address, EventType, error)

Jump to

Keyboard shortcuts

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