contract

package
v0.0.0-...-c848cff Latest Latest
Warning

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

Go to latest
Published: Nov 5, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Overview

Package contract publishes Tether's admin operations to the Forge dashboard as a contract contributor: a declarative manifest plus typed handlers that call the same services the REST API in package api does.

The extension registers it from RegisterContractContributor. Every intent is admitted only for a principal with an authenticated user (set Deps.AllowAnonymous to turn that off), because the dashboard's auth is off unless the host enables it.

A signed-in user is then confined to their own tenant: the one their session names, through a tenant_id claim or the org of the forge Scope the host's auth middleware sets, mapped to a Tether tenant. Naming another tenant is PERMISSION_DENIED, leaving tenantId out means their own, and a user with no tenant is refused. Deps.Operators lifts that: an operator may act on any tenant and manage tenants themselves. session.info tells the plugin which of the two the caller is.

Index

Constants

View Source
const ContributorName = "tether"

ContributorName is the contributor name in the manifest and on the wire.

Variables

This section is empty.

Functions

func Register

Register loads the embedded manifest, validates it, registers it with the dashboard's contract registry and wires one handler per intent into disp.

Types

type Ack

type Ack struct {
	OK bool `json:"ok"`
}

Ack answers a command with nothing to return.

type Client

type Client struct {
	ClientID             string     `json:"clientId"`
	Username             string     `json:"username,omitempty"`
	DeviceID             string     `json:"deviceId,omitempty"`
	Remote               string     `json:"remote,omitempty"`
	Connected            bool       `json:"connected"`
	ProtocolVersion      int        `json:"protocolVersion"`
	Subscriptions        []string   `json:"subscriptions"`
	ConnectedAt          *time.Time `json:"connectedAt,omitempty"`
	SessionExpirySeconds uint32     `json:"sessionExpirySeconds"`
}

Client is one of a tenant's sessions on this node.

type ClientList

type ClientList struct {
	Items []Client `json:"items"`
}

ClientList is the answer of clients.list.

type ClientRef

type ClientRef struct {
	TenantID string `json:"tenantId"`
	ClientID string `json:"clientId"`
}

ClientRef names a client: clients.kick, sessions.delete.

type CreateRoleInput

type CreateRoleInput struct {
	TenantID   string   `json:"tenantId"`
	Slug       string   `json:"slug"`
	Name       string   `json:"name"`
	ParentSlug string   `json:"parentSlug"`
	Publish    []string `json:"publish"`
	Subscribe  []string `json:"subscribe"`
}

CreateRoleInput is roles.create. Name defaults to the slug, ParentSlug names a role of the same tenant to inherit from, and the lists are the role's topic grants (both optional, empty grants nothing).

type CreateTenantInput

type CreateTenantInput struct {
	AppID      string `json:"appId"`
	ExternalID string `json:"externalId"`
	Name       string `json:"name"`
	Limits     Limits `json:"limits"`
}

CreateTenantInput is tenants.create.

type Credentials

type Credentials struct {
	Device   Device `json:"device"`
	Username string `json:"username"`
	Password string `json:"password"`
}

Credentials is the answer of devices.provision and devices.rotate: the device, the MQTT username (its keysmith key ID) and the raw key, the MQTT password. It is shown once and kept nowhere.

type Deps

type Deps struct {
	Services ServicesProvider

	// AllowAnonymous serves requests whose principal has no authenticated
	// user. The default refuses them with UNAUTHENTICATED, before any
	// handler runs: the dashboard's auth is off unless the host turns it on,
	// and these intents provision device keys and delete tenants. A
	// principal has no user when the dashboard's auth is off, and also when
	// it is on but the request did not authenticate.
	AllowAnonymous bool

	// Operators makes every caller an operator, who may name any tenant and
	// create, change and delete tenants. Without it a caller is confined to
	// their own tenant (see the package doc). AllowAnonymous implies it: an
	// anonymous caller has no session to take a tenant from.
	Operators bool
}

Deps are the data sources the handlers need.

type Device

type Device struct {
	ID        string         `json:"id"`
	TenantID  string         `json:"tenantId"`
	Name      string         `json:"name"`
	KeyID     string         `json:"keyId"`
	ClientID  string         `json:"clientId,omitempty"`
	Roles     []string       `json:"roles"`
	Metadata  map[string]any `json:"metadata,omitempty"`
	CreatedAt time.Time      `json:"createdAt"`
	UpdatedAt time.Time      `json:"updatedAt"`
}

Device is one MQTT client identity inside a tenant. It never carries the key's secret; only Credentials does, once.

type DeviceList

type DeviceList struct {
	Items  []Device `json:"items"`
	Limit  int      `json:"limit"`
	Offset int      `json:"offset"`
}

DeviceList is the answer of devices.list.

type DeviceListInput

type DeviceListInput struct {
	TenantID string `json:"tenantId"`
	Limit    int    `json:"limit"`
	Offset   int    `json:"offset"`
}

DeviceListInput is devices.list.

type DeviceRef

type DeviceRef struct {
	TenantID string `json:"tenantId"`
	DeviceID string `json:"deviceId"`
}

DeviceRef names a device: devices.get, devices.delete.

type Limits

type Limits struct {
	MaxConnections          int     `json:"maxConnections"`
	MessagesPerSecond       float64 `json:"messagesPerSecond"`
	Burst                   int     `json:"burst"`
	MaxSessionExpirySeconds int64   `json:"maxSessionExpirySeconds"`
	MaxSessions             int     `json:"maxSessions"`
}

Limits caps what one tenant may do. Zero means the broker default, and unlimited for maxConnections, messagesPerSecond and maxSessions (see tenant.Limits).

type PageInput

type PageInput struct {
	Limit  int `json:"limit"`
	Offset int `json:"offset"`
}

PageInput pages tenants.list. Zero limit means the default page.

type ProvisionInput

type ProvisionInput struct {
	TenantID  string     `json:"tenantId"`
	Name      string     `json:"name"`
	ClientID  string     `json:"clientId"`
	Roles     []string   `json:"roles"`
	ExpiresAt *time.Time `json:"expiresAt"`
}

ProvisionInput is devices.provision.

type Retained

type Retained struct {
	Topic string `json:"topic"`
	Size  int    `json:"size"`
	QoS   int    `json:"qos"`
}

Retained is one retained message, without its payload.

type RetainedList

type RetainedList struct {
	Items []Retained `json:"items"`
}

RetainedList is the answer of retained.list.

type RetainedListInput

type RetainedListInput struct {
	TenantID string `json:"tenantId"`
	Filter   string `json:"filter"`
}

RetainedListInput is retained.list. An empty Filter is "#", every topic but $SYS.

type RetainedRef

type RetainedRef struct {
	TenantID string `json:"tenantId"`
	Topic    string `json:"topic"`
}

RetainedRef names a retained topic: retained.delete.

type RevokeInput

type RevokeInput struct {
	TenantID string `json:"tenantId"`
	DeviceID string `json:"deviceId"`
	Reason   string `json:"reason"`
}

RevokeInput is devices.revoke.

type Role

type Role struct {
	Slug       string   `json:"slug"`
	Name       string   `json:"name"`
	ParentSlug string   `json:"parentSlug,omitempty"`
	Publish    []string `json:"publish"`
	Subscribe  []string `json:"subscribe"`
}

Role is a role of a tenant with its topic grants. Each list is never null.

type RoleList

type RoleList struct {
	Items []Role `json:"items"`
}

RoleList is the answer of roles.list.

type RoleRef

type RoleRef struct {
	TenantID string `json:"tenantId"`
	RoleSlug string `json:"roleSlug"`
}

RoleRef names a role: roles.topics.get and roles.delete.

type RotateInput

type RotateInput struct {
	TenantID     string `json:"tenantId"`
	DeviceID     string `json:"deviceId"`
	GraceSeconds int64  `json:"graceSeconds"`
}

RotateInput is devices.rotate. The old key keeps working for GraceSeconds; zero takes keysmith's default.

type ServicesProvider

type ServicesProvider func() *api.Services

ServicesProvider returns the services the handlers run on, or nil while Tether has not started. It is called on every request, so a contributor registered by the dashboard before Tether's Start still works afterwards.

type SessionInfo

type SessionInfo struct {
	Operator bool   `json:"operator"`
	TenantID string `json:"tenantId,omitempty"`
}

SessionInfo is the answer of session.info: whether the caller is an operator, and the caller's own tenant when it has one. The plugin reads it on load to pick its pages.

type SetDeviceRolesInput

type SetDeviceRolesInput struct {
	TenantID string   `json:"tenantId"`
	DeviceID string   `json:"deviceId"`
	Roles    []string `json:"roles"`
}

SetDeviceRolesInput is devices.roles.set. Roles is the device's whole set of role slugs: required, and [] removes them all.

type SetTopicsInput

type SetTopicsInput struct {
	TenantID  string   `json:"tenantId"`
	RoleSlug  string   `json:"roleSlug"`
	Publish   []string `json:"publish"`
	Subscribe []string `json:"subscribe"`
}

SetTopicsInput is roles.topics.set. It replaces both lists, so both are required; [] clears one.

type Stats

type Stats struct {
	Scope         string `json:"scope"`
	TenantID      string `json:"tenantId,omitempty"`
	Tenants       *int   `json:"tenants,omitempty"`
	Connected     int    `json:"connected"`
	Sessions      int    `json:"sessions"`
	Subscriptions int    `json:"subscriptions"`
	Retained      int    `json:"retained"`
	MessagesIn    uint64 `json:"messagesIn"`
	MessagesOut   uint64 `json:"messagesOut"`
}

Stats is the answer of overview.stats. Scope is "tenant" with a TenantID, or "node" with Tenants, the number of tenants, and every other count summed over them.

type StatsInput

type StatsInput struct {
	TenantID string `json:"tenantId"`
}

StatsInput is overview.stats. An empty TenantID is the node total.

type Tenant

type Tenant struct {
	ID         string    `json:"id"`
	AppID      string    `json:"appId"`
	ExternalID string    `json:"externalId"`
	Name       string    `json:"name"`
	Enabled    bool      `json:"enabled"`
	Limits     Limits    `json:"limits"`
	CreatedAt  time.Time `json:"createdAt"`
	UpdatedAt  time.Time `json:"updatedAt"`
}

Tenant is one isolated customer of the broker.

type TenantList

type TenantList struct {
	Items  []Tenant `json:"items"`
	Limit  int      `json:"limit"`
	Offset int      `json:"offset"`
}

TenantList is the answer of tenants.list.

type TenantRef

type TenantRef struct {
	TenantID string `json:"tenantId"`
}

TenantRef names a tenant: tenants.get, tenants.delete, clients.list.

type TopicRules

type TopicRules struct {
	RoleSlug  string   `json:"roleSlug"`
	Publish   []string `json:"publish"`
	Subscribe []string `json:"subscribe"`
}

TopicRules is a role's topic grants. Each list is never null.

type UpdateTenantInput

type UpdateTenantInput struct {
	TenantID string  `json:"tenantId"`
	Name     *string `json:"name"`
	Enabled  *bool   `json:"enabled"`
	Limits   *Limits `json:"limits"`
}

UpdateTenantInput is tenants.update. A field left out stays as it is; a present Limits replaces all four limits.

Jump to

Keyboard shortcuts

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