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
- func Register(disp *dispatcher.Dispatcher, reg dashcontract.Registry, ...) error
- type Ack
- type Client
- type ClientList
- type ClientRef
- type CreateRoleInput
- type CreateTenantInput
- type Credentials
- type Deps
- type Device
- type DeviceList
- type DeviceListInput
- type DeviceRef
- type Limits
- type PageInput
- type ProvisionInput
- type Retained
- type RetainedList
- type RetainedListInput
- type RetainedRef
- type RevokeInput
- type Role
- type RoleList
- type RoleRef
- type RotateInput
- type ServicesProvider
- type SessionInfo
- type SetDeviceRolesInput
- type SetTopicsInput
- type Stats
- type StatsInput
- type Tenant
- type TenantList
- type TenantRef
- type TopicRules
- type UpdateTenantInput
Constants ¶
const ContributorName = "tether"
ContributorName is the contributor name in the manifest and on the wire.
Variables ¶
This section is empty.
Functions ¶
func Register ¶
func Register(disp *dispatcher.Dispatcher, reg dashcontract.Registry, wreg dashcontract.WardenRegistry, deps Deps) error
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 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 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 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 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 RetainedList ¶
type RetainedList struct {
Items []Retained `json:"items"`
}
RetainedList is the answer of retained.list.
type RetainedListInput ¶
RetainedListInput is retained.list. An empty Filter is "#", every topic but $SYS.
type RetainedRef ¶
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 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 ¶
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.