Documentation
¶
Index ¶
- Constants
- Variables
- func GenerateAPIKey() (rawKey string, keyHash string, keyPrefix string, error error)
- func HashAPIKey(rawKey string) string
- func ParseAndValidateIP(ipInput string) (netip.Addr, error)
- 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 AddressObserver
- type CreateAddressParams
- type CreateDeviceInput
- type CreateDeviceParams
- type Device
- 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) DeleteDeviceAPIKey(ctx context.Context, request httpapi.DeleteDeviceAPIKeyRequestObject) (httpapi.DeleteDeviceAPIKeyResponseObject, error)
- func (h *HTTPHandler) DeviceHeartbeat(ctx context.Context, request httpapi.DeviceHeartbeatRequestObject) (httpapi.DeviceHeartbeatResponseObject, error)
- func (h *HTTPHandler) DeviceHeartbeatByAPIKey(ctx context.Context, _ httpapi.DeviceHeartbeatByAPIKeyRequestObject) (httpapi.DeviceHeartbeatByAPIKeyResponseObject, error)
- func (h *HTTPHandler) DisableAddress(ctx context.Context, request httpapi.DisableAddressRequestObject) (httpapi.DisableAddressResponseObject, error)
- func (h *HTTPHandler) DisableDevice(ctx context.Context, request httpapi.DisableDeviceRequestObject) (httpapi.DisableDeviceResponseObject, error)
- func (h *HTTPHandler) EnableDevice(ctx context.Context, request httpapi.EnableDeviceRequestObject) (httpapi.EnableDeviceResponseObject, 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 ids.DeviceID, addressID ids.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) DeleteAPIKey(ctx context.Context, deviceID ids.DeviceID) error
- func (r *Repository) DeleteAddressEventsOlderThan(ctx context.Context, before time.Time) (int64, error)
- func (r *Repository) DeleteDevice(ctx context.Context, deviceID ids.DeviceID) error
- func (r *Repository) DisableAddress(ctx context.Context, addressID ids.AddressID) (*Address, error)
- func (r *Repository) DisableAddresses(ctx context.Context, addressIDs []ids.AddressID, source EventSource) ([]Address, error)
- func (r *Repository) EnableAddress(ctx context.Context, addressID ids.AddressID, source EventSource) (*Address, error)
- func (r *Repository) GetAddress(ctx context.Context, addressID ids.AddressID) (*Address, error)
- func (r *Repository) GetAddressForDeviceByIP(ctx context.Context, deviceID ids.DeviceID, ip netip.Addr) (*Address, error)
- func (r *Repository) GetDevice(ctx context.Context, id ids.DeviceID) (*Device, error)
- func (r *Repository) GetDeviceByAPIKeyHash(ctx context.Context, keyHash string) (*Device, error)
- func (r *Repository) GetDeviceIDsByOwner(ctx context.Context, ownerID ids.UserID) ([]ids.DeviceID, error)
- func (r *Repository) GetEnabledAddressesForDevice(ctx context.Context, deviceID ids.DeviceID) ([]Address, error)
- func (r *Repository) GetEnabledIPEntries(ctx context.Context) ([]IPEntry, error)
- func (r *Repository) IsDeviceDisabled(ctx context.Context, id ids.DeviceID) (bool, error)
- func (r *Repository) RefreshAddress(ctx context.Context, addressID ids.AddressID, source EventSource) (*Address, error)
- func (r *Repository) SetDeviceDisabled(ctx context.Context, deviceID ids.DeviceID, disabled bool) error
- func (r *Repository) UpdateDevice(ctx context.Context, device *Device) (*Device, error)
- func (r *Repository) UpsertAPIKey(ctx context.Context, deviceID ids.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, principal *auth.Principal, name string, ...) (*Device, error)
- func (s *Service) CreateDeviceWithOptions(ctx context.Context, principal *auth.Principal, input CreateDeviceInput) (*Device, string, error)
- func (s *Service) DeleteAPIKey(ctx context.Context, deviceID ids.DeviceID) error
- func (s *Service) DeleteDevice(ctx context.Context, deviceID ids.DeviceID) error
- func (s *Service) DisableAddress(ctx context.Context, deviceID ids.DeviceID, addressID ids.AddressID) (*Address, error)
- func (s *Service) DisableAddresses(ctx context.Context, addressIDs []ids.AddressID, source EventSource) error
- func (s *Service) DisableDevice(ctx context.Context, deviceID ids.DeviceID) (*Device, error)
- func (s *Service) EnableDevice(ctx context.Context, deviceID ids.DeviceID) (*Device, error)
- func (s *Service) GetDevice(ctx context.Context, deviceID ids.DeviceID) (*Device, error)
- func (s *Service) GetEnabledAddressesForDevice(ctx context.Context, deviceID ids.DeviceID) ([]Address, error)
- func (s *Service) GetEnabledIPEntries(ctx context.Context) ([]IPEntry, error)
- func (s *Service) OnUserEvent(ctx context.Context, event auth.UserEvent)
- func (s *Service) RegenerateAPIKey(ctx context.Context, deviceID ids.DeviceID) (*Device, string, error)
- func (s *Service) RegisterAddressActivity(ctx context.Context, deviceID ids.DeviceID, inputIP string, source EventSource) (*Address, EventType, error)
- func (s *Service) UpdateDevice(ctx context.Context, deviceID ids.DeviceID, input UpdateDeviceInput) (*Device, error)
- type UpdateDeviceInput
Constants ¶
const ( EventSourceHeartbeat = httpapi.Heartbeat EventSourceManual = httpapi.Manual EventSourceExpiry = httpapi.Expiry EventSourceLimitExceeded = httpapi.LimitExceeded )
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") ErrDeviceDisabled = errors.New("device is disabled") ErrOwnerNotFound = errors.New("device owner not found") ErrNoAPIKey = errors.New("device has no API key") 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") 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") )
Functions ¶
func GenerateAPIKey ¶ added in v0.3.0
GenerateAPIKey generates a new API key and returns the raw key (to send to user), the key hash (to store in DB), and the key prefix (for display).
func HashAPIKey ¶ added in v0.3.0
HashAPIKey hashes the API key using SHA-256 and returns base64url encoded hash. This mirrors the pattern used in auth.hashRawToken.
func ParseAndValidateIP ¶ added in v0.3.0
ParseAndValidateIP parses and validates that the given string is a valid IPv4 or IPv6 address. It ignores the port if present and only cares about the IP component. The result is always canonical: an IPv4-mapped IPv6 address (::ffff:a.b.c.d) is unmapped to its plain IPv4 form so stored addresses never depend on representation. TODO: Make private once the address_test go through the NewCreateAddressParams
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 ids.AddressID `db:"id"`
DeviceID ids.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 ids.AddressID
DeviceID ids.DeviceID
OccurredAt time.Time
}
func NewAddressEvent ¶
func NewAddressEvent(address *Address, eventType EventType) AddressEvent
func NewDeviceEvent ¶ added in v0.3.0
func NewDeviceEvent(deviceID ids.DeviceID, 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 AddressObserver ¶
type AddressObserver interface {
OnAddressEvent(ctx context.Context, event AddressEvent)
}
type CreateAddressParams ¶
CreateAddressParams holds only what is necessary to create an address.
func NewCreateAddressParams ¶
type CreateDeviceInput ¶ added in v0.4.0
type CreateDeviceInput struct {
Name string
OwnerID *ids.UserID // nil = owned by the calling principal
Description *string
Icon *string
GenerateAPIKey bool // mint an API key in the same transaction, returned once
}
CreateDeviceInput carries the full set of create-time choices. Profile fields and the credential are optional — a device is valid with only a name and owner, so the bare CreateDevice below is just this with everything else zero.
type CreateDeviceParams ¶
type Device ¶
type Device struct {
ID ids.DeviceID `db:"id"`
Name string `db:"name"`
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"`
DisabledAt *time.Time `db:"disabled_at"`
KeyPrefix *string `db:"key_prefix"`
OwnerID ids.UserID `db:"owner_id"`
}
func (*Device) Update ¶ added in v0.2.0
func (d *Device) Update(name *string, description **string, icon **string, ownerID *ids.UserID) 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)
- description: nil = keep current; non-nil ptr to nil = clear; non-nil ptr to value = set
- icon: same semantics as description
type EventSource ¶
type EventSource = httpapi.AddressEventSource
EventSource is an alias for the API-generated type, making openapi.yaml the single source of truth for valid values.
type EventType ¶
type EventType string
const ( EventTypeAddressCreated EventType = "address_created" EventTypeAddressEnabled EventType = "address_enabled" EventTypeAddressRefreshed EventType = "address_refreshed" EventTypeAddressDisabled EventType = "address_disabled" EventTypeDeviceOwnershipChanged EventType = "device_ownership_changed" )
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) DeleteDeviceAPIKey ¶ added in v0.3.0
func (h *HTTPHandler) DeleteDeviceAPIKey(ctx context.Context, request httpapi.DeleteDeviceAPIKeyRequestObject) (httpapi.DeleteDeviceAPIKeyResponseObject, 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, _ httpapi.DeviceHeartbeatByAPIKeyRequestObject) (httpapi.DeviceHeartbeatByAPIKeyResponseObject, error)
func (*HTTPHandler) DisableAddress ¶
func (h *HTTPHandler) DisableAddress(ctx context.Context, request httpapi.DisableAddressRequestObject) (httpapi.DisableAddressResponseObject, error)
func (*HTTPHandler) DisableDevice ¶ added in v0.4.0
func (h *HTTPHandler) DisableDevice(ctx context.Context, request httpapi.DisableDeviceRequestObject) (httpapi.DisableDeviceResponseObject, error)
func (*HTTPHandler) EnableDevice ¶ added in v0.4.0
func (h *HTTPHandler) EnableDevice(ctx context.Context, request httpapi.EnableDeviceRequestObject) (httpapi.EnableDeviceResponseObject, 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 ids.DeviceID `db:"device_id"`
AddressID ids.AddressID `db:"address_id"`
UserID ids.UserID `db:"user_id"`
}
IPEntry associates an enabled IP address with the device, address, and owning user. All enabled rows are returned (multiple per IP when devices share an address); the policy layer merges them with deny-wins intersection.
type Repository ¶
type Repository struct {
// contains filtered or unexported fields
}
func NewRepository ¶
func NewRepository(db *database.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) DeleteAPIKey ¶ added in v0.3.0
func (*Repository) DeleteAddressEventsOlderThan ¶ added in v0.4.0
func (r *Repository) DeleteAddressEventsOlderThan(ctx context.Context, before time.Time) (int64, error)
DeleteAddressEventsOlderThan removes address_events rows with created_at before the given time. Returns the number of rows deleted.
func (*Repository) DeleteDevice ¶
func (*Repository) DisableAddress ¶
func (*Repository) DisableAddresses ¶
func (r *Repository) DisableAddresses(ctx context.Context, addressIDs []ids.AddressID, source EventSource) ([]Address, error)
func (*Repository) EnableAddress ¶
func (r *Repository) EnableAddress(ctx context.Context, addressID ids.AddressID, source EventSource) (*Address, error)
func (*Repository) GetAddress ¶
func (*Repository) GetAddressForDeviceByIP ¶
func (*Repository) GetDeviceByAPIKeyHash ¶
func (*Repository) GetDeviceIDsByOwner ¶ added in v0.4.0
func (*Repository) GetEnabledAddressesForDevice ¶ added in v0.2.0
func (r *Repository) GetEnabledAddressesForDevice(ctx context.Context, deviceID ids.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) IsDeviceDisabled ¶ added in v0.4.0
IsDeviceDisabled reports whether a device has been disabled (disabled_at set). A missing or soft-deleted device returns ErrDeviceNotFound, so callers can use this as both an existence and a state check.
func (*Repository) RefreshAddress ¶
func (r *Repository) RefreshAddress(ctx context.Context, addressID ids.AddressID, source EventSource) (*Address, error)
RefreshAddress records activity for an already-enabled address
func (*Repository) SetDeviceDisabled ¶ added in v0.4.0
func (r *Repository) SetDeviceDisabled(ctx context.Context, deviceID ids.DeviceID, disabled bool) error
SetDeviceDisabled flips a device's disabled state. When disabled is true the disabled_at timestamp is stamped; when false it is cleared (re-enabled).
func (*Repository) UpdateDevice ¶ added in v0.2.0
func (*Repository) UpsertAPIKey ¶ added in v0.3.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 (s *Service) CreateDevice(ctx context.Context, principal *auth.Principal, name string, requestedOwnerID *ids.UserID) (*Device, error)
CreateDevice creates a bare device (name + owner only). It is the primitive a device needs to exist; richer creation goes through CreateDeviceWithOptions.
func (*Service) CreateDeviceWithOptions ¶ added in v0.4.0
func (s *Service) CreateDeviceWithOptions(ctx context.Context, principal *auth.Principal, input CreateDeviceInput) (*Device, string, error)
CreateDeviceWithOptions creates a device and, when requested, mints its API key in the same transaction so a "shown once" key is never stranded on a half-created device. The raw key is returned only when GenerateAPIKey is set. Pairing codes and address rules are provisioned separately on their own endpoints (a credential-less device is perfectly valid).
func (*Service) DeleteAPIKey ¶ added in v0.3.0
func (*Service) DeleteDevice ¶
func (*Service) DisableAddress ¶
func (*Service) DisableAddresses ¶
func (*Service) DisableDevice ¶ added in v0.4.0
DisableDevice is a reversible freeze: it disables every active address in one transaction (removing the device's IPs from the access allowlist) and stamps disabled_at, which blocks address enable/refresh until the device is re-enabled. The API key is deliberately kept — disable is a safety toggle, not a de-credentialing (use DeleteAPIKey for that). EnableDevice reverses it.
func (*Service) EnableDevice ¶ added in v0.4.0
EnableDevice reverses DisableDevice by clearing the disabled_at flag, which re-permits address enable/refresh. The device's API key was never removed, so it can heartbeat its addresses back to enabled on its next check-in; addresses disabled at freeze time are not re-enabled here.
func (*Service) GetEnabledAddressesForDevice ¶ added in v0.2.0
func (s *Service) GetEnabledAddressesForDevice(ctx context.Context, deviceID ids.DeviceID) ([]Address, error)
GetEnabledAddressesForDevice returns all enabled addresses for a device, ordered by updated_at DESC.
func (*Service) GetEnabledIPEntries ¶
func (*Service) OnUserEvent ¶ added in v0.4.0
OnUserEvent implements auth.UserObserver. On deletion it soft-deletes every device owned by the user and disables their addresses, firing the AddressDisabled events that trigger a policy cache refresh.
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
Description **string
Icon **string
OwnerID *ids.UserID
}
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.