device

package
v0.1.11 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package device implements the peer/device domain service. The two invariants the product can never afford to break are enforced here inside BEGIN IMMEDIATE transactions (database.md): the per-user device limit and the unique per-interface IPv4 allocation. Keys arrive encrypted by the caller (secrets.KeyRing); this package never sees plaintext private keys in logs.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Device

type Device struct {
	ID            string
	UserID        string
	InterfaceID   string
	Name          string
	IPv4          string // with /32
	PublicKey     string
	PrivKeyEnc    []byte
	PSKEnc        []byte // nil = no preshared key
	Enabled       bool
	LastHandshake *time.Time
	LastEndpoint  string
	RXBytes       uint64
	TXBytes       uint64
	LastRX        uint64
	LastTX        uint64
	CreatedAt     time.Time
	UpdatedAt     time.Time
}

Device is a stored peer.

type KeyMaterial

type KeyMaterial struct {
	PublicKey     string
	PrivateKeyEnc []byte // AES-GCM envelope from secrets.KeyRing
	PresharedEnc  []byte // optional; nil = none
}

KeyMaterial is the caller-provided keypair for a new device.

func GenerateKeys added in v0.1.10

func GenerateKeys(ring *secrets.KeyRing, withPSK bool) (*KeyMaterial, error)

GenerateKeys is the shared provisioning boundary for device credentials. Only sealed material and its public identity leave it; it performs no storage, rendering or host operation. API/web parsing and principal checks stay outside.

type Rotation

type Rotation struct {
	DeviceID string
	Keys     KeyMaterial
}

Rotation pairs a stored device identity with replacement key material. It is used by coordinated access-rotation workflows that must replace every credential for one user in the same database transaction.

type Service

type Service struct {

	// Recorder (optional, satisfied by *webhook.Recorder) emits durable
	// device events inside the state-changing transaction (webhooks.md).
	Recorder interface {
		RecordTx(tx *sql.Tx, eventType string, data map[string]any) error
	}
	// contains filtered or unexported fields
}

Service holds device rules.

func NewService

func NewService(db *database.DB, ring *secrets.KeyRing) *Service

func (*Service) CountForIfaces

func (s *Service) CountForIfaces(ctx context.Context, ifaceIDs []string) (map[string]int, error)

CountForIfaces returns device counts per interface id (batch for the interface list). Counts all devices — including those of soft-deleted users — to mirror the interface Delete guard.

func (*Service) CountForUser

func (s *Service) CountForUser(ctx context.Context, userID string) (int, error)

CountForUser returns the live device count.

func (*Service) CountForUsers

func (s *Service) CountForUsers(ctx context.Context, userIDs []string) (map[string]int, error)

CountForUsers returns device counts for a batch of users (one query per page, not per row). Missing IDs count 0.

func (*Service) Create

func (s *Service) Create(ctx context.Context, userID string, name string, keys KeyMaterial, interfaceID string) (*Device, error)

Create provisions a device for a user inside one immediate transaction: limit check → free-IP allocation → insert. Concurrent creates serialize on the write lock, so the limit can never be exceeded by a race (tested).

func (*Service) CreateTx added in v0.1.3

func (s *Service) CreateTx(ctx context.Context, tx *sql.Tx, userID string, name string, keys KeyMaterial, interfaceID string) (*Device, error)

CreateTx provisions a peer in an existing write transaction. The caller can commit the user, device and operation result together; allocation and device-limit checks retain their serialized transaction boundary.

func (*Service) Delete

func (s *Service) Delete(ctx context.Context, id string) error

Delete permanently removes a device and releases its VPN IP.

func (*Service) Get

func (s *Service) Get(ctx context.Context, id string) (*Device, error)

Get loads a device by ID.

func (*Service) ListForUser

func (s *Service) ListForUser(ctx context.Context, userID string) ([]*Device, error)

ListForUser returns the user's devices, newest first.

func (*Service) ListForUsers

func (s *Service) ListForUsers(ctx context.Context, userIDs []string) (map[string][]*Device, error)

ListForUsers batch-loads the devices of a page of users (one query per page for the list-view quick-share menus), newest first within each user.

func (*Service) PresharedKey

func (s *Service) PresharedKey(ctx context.Context, d *Device) (string, error)

PresharedKey decrypts the stored preshared key ("" when none).

func (*Service) PrivateKey

func (s *Service) PrivateKey(ctx context.Context, d *Device) (string, error)

PrivateKey decrypts the stored private key (config generation and peer apply only — never logged, never serialized to API responses).

func (*Service) Regenerate

func (s *Service) Regenerate(ctx context.Context, id string, keys KeyMaterial) error

Regenerate replaces the device keys (caller supplies the new encrypted keypair; the public key change propagates to the backend via reconcile).

func (*Service) Rename

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

Rename changes the device display name (unique per user).

func (*Service) ReplaceUserCredentialsTx

func (s *Service) ReplaceUserCredentialsTx(ctx context.Context, tx *sql.Tx, userID string, rotations []Rotation) error

ReplaceUserCredentialsTx replaces the credentials for exactly all devices currently owned by userID. The exact-set check prevents a concurrent or incomplete caller snapshot from leaving one old client credential valid. The caller owns tx and may atomically rotate another access capability in the same transaction.

func (*Service) SetEnabled

func (s *Service) SetEnabled(ctx context.Context, id string, enabled bool) error

SetEnabled toggles a device (disabled devices are removed from the backend by reconciliation).

Jump to

Keyboard shortcuts

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