models

package
v0.27.0-rc.9 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2026 License: Apache-2.0 Imports: 10 Imported by: 20

Documentation

Index

Constants

View Source
const (
	// DeviceLoginCodeKindDevice is a code bound to an existing pending device
	// in a namespace (agent had a tenant).
	DeviceLoginCodeKindDevice = "device"
	// DeviceLoginCodeKindPairing is a code for a tenant-less agent; the device
	// does not exist yet and the user picks the namespace at accept time.
	DeviceLoginCodeKindPairing = "pairing"
)

Kinds of codes the accept-device page can resolve.

View Source
const (
	// InstallKeyWebhookDefaultTimeout / MaxTimeout bound the synchronous webhook request.
	InstallKeyWebhookDefaultTimeout = 5
	InstallKeyWebhookMaxTimeout     = 15
	// InstallKeyWebhookDefaultCallbackTTL / MaxCallbackTTL bound the deferred-decision token's validity
	// (1 hour default, 24 hours max).
	InstallKeyWebhookDefaultCallbackTTL = 3600
	InstallKeyWebhookMaxCallbackTTL     = 86400
)

Webhook tuning bounds/defaults (seconds). A stored 0 means "use the default".

View Source
const (
	// MemberStatusActive is a completed, confirmed account that is a full member.
	MemberStatusActive = "active"
	// MemberStatusAwaitingApproval is a completed account still waiting for a system admin to
	// approve it; it is already a member but cannot sign in yet (see the auth login gate).
	MemberStatusAwaitingApproval = "awaiting_approval"
	// MemberStatusNotConfirmed is a member whose account was provisioned by the invite but not
	// yet completed; the invitee still has to finish setting it up.
	MemberStatusNotConfirmed = "not-confirmed"
)

Member status values used by MemberView.Status. They flatten a member's account state into a single field the members list renders. Both statuses derive from core-only concepts (the users.awaiting_approval flag and the login gate). Cloud/enterprise may extend the view with additional statuses (e.g. pending invitations) in its own response type — kept out of core.

View Source
const (
	// SSHAccessModeLegacy is the key/firewall model: access is a public key with
	// an ACL plus, on Cloud/Enterprise, firewall rules.
	SSHAccessModeLegacy = "legacy"
	// SSHAccessModeIdentity is the identity model: access is a ShellHub identity
	// (established by the out-of-band browser approval) plus Access Policies
	// deciding who may reach what, as which login. The legacy key ACL and
	// firewall checks are bypassed.
	SSHAccessModeIdentity = "identity"
)

SSHAccessMode selects how a namespace authorizes SSH access.

View Source
const EnrollmentReconcileInterval = 1 * time.Minute

EnrollmentReconcileInterval throttles re-evaluation of a still-pending enrollment on the agent's periodic AuthDevice. It is a server-side anti-hammer floor whose only job is to bound a fast crash-looping agent to one integrator call per interval; the real reconcile cadence is the agent's ping (~10m), well above this. Kept short (1m) so a legitimate restart/reconnect reconciles promptly instead of being silently skipped, while a per-second re-auth loop is still capped at 1/min.

View Source
const InstanceAPIKeyPrefix = "sh_admin_"

InstanceAPIKeyPrefix marks a plaintext key as an instance API key. Authentication branches on it before reaching a store, so the credential itself states which kind of key it is instead of the server inferring it from a failed namespace lookup.

Variables

This section is empty.

Functions

func IsTypePersonal added in v0.18.0

func IsTypePersonal(typeNamespace string) bool

IsTypePersonal reports whether the raw string names the personal type. An unrecognized value is not personal, and neither predicate is the negation of the other.

func IsTypeTeam added in v0.18.0

func IsTypeTeam(typeNamespace string) bool

IsTypeTeam reports whether the raw string names the team type. An unrecognized value is not a team, so a corrupted or future value degrades to the more restrictive answer.

func MatchPattern

func MatchPattern(pattern, value string) (bool, error)

MatchPattern reports whether value satisfies an access rule's pattern. The pattern is a regular expression that must match value in full, so the rule "staging" selects the device named "staging" and neither "notstaging" nor "staging-db"; an empty pattern imposes no restriction and matches any value. It returns an error when the pattern does not compile.

It is the shared matcher for every administrator-written selector that decides access — device filters, public-key username restrictions and firewall rules — so that all of them read the way their author wrote them.

Types

type APIKey added in v0.15.0

type APIKey struct {
	// ID is the unique identifier of the API key. It is a SHA256 hash of a UUID.
	ID string `json:"-"`
	// Name is an external identifier for a given API key. It is not unique per document but
	// is unique per tenant ID.
	Name string `json:"name"`
	// TenantID is the API key's namespace ID.
	TenantID string `json:"tenant_id"`
	// Role defines the permissions of the API key. It must be equal to or less than the creator's role.
	Role authorizer.Role `json:"role" validate:"required,oneof=administrator operator observer"`
	// CreatedBy is the ID of the user who created the API key.
	CreatedBy string `json:"created_by"`
	// CreatedAt is the creation date of the API key.
	CreatedAt time.Time `json:"created_at"`
	// UpdatedAt is the last update date of the API key.
	UpdatedAt time.Time `json:"updated_at"`
	// ExpiresIn is the expiration date of the API key. An expired key cannot be used for
	// authentication. When equals or less than 0 it means that are no expiration date.
	ExpiresIn int64 `json:"expires_in"`
}

APIKey is used to authenticate a request. It is similar to [UserAuthClaims] but only for namespace information, which means that user-related routes are blocked for use with api keys. The ID and key are never returned to the end user; the "external" identification must be made by name and tenant only.

Expired keys cannot be used for authentication. Use APIKey.IsValid to verify its validity.

func (*APIKey) IsValid added in v0.16.0

func (a *APIKey) IsValid() bool

IsValid reports whether an API key is valid or not.

type APIKeyConflicts added in v0.16.0

type APIKeyConflicts struct {
	ID   string
	Name string
}

APIKeyConflicts holds API keys attributes that must be unique for each item (per tenant ID) and can be utilized in queries to identify conflicts.

type AccessPolicy

type AccessPolicy struct {
	ID       string          `json:"id"`
	TenantID string          `json:"-"`
	Name     string          `json:"name"`
	Subject  PolicySubject   `json:"subject"`
	Filter   PublicKeyFilter `json:"filter"`
	// Logins are the unix logins this policy covers: exact names, or ["*"] for
	// any login.
	Logins []string `json:"logins"`
	// SourceIP restricts the policy to connections from these CIDRs (a client IP
	// in any of them matches). Empty matches any IP. A single host is a /32 (or
	// /128 for IPv6).
	SourceIP []string `json:"source_ip"`
	// Action is whether this policy grants (allow) or blocks (deny) the covered
	// access. Defaults to allow.
	Action PolicyAction `json:"action"`
	// RequireReauth gates access granted by this policy on a fresh
	// re-authentication (an out-of-band confirmation), even when the connecting
	// key is already an identity. Off by default; the identity alone is the norm.
	RequireReauth bool `json:"require_reauth"`
	// ReauthPeriod is the freshness window for RequireReauth, in seconds: a
	// re-authentication is only demanded when the identity has not re-authed
	// within it. The freshness belongs to the identity, not to a connection, so
	// within the window every login with that key goes straight through. nil or 0
	// means "always", the only setting that is genuinely per login. Only
	// meaningful when RequireReauth is set.
	ReauthPeriod *int `json:"reauth_period"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

AccessPolicy is a namespace-scoped authorization rule for the identity-based SSH access mode: for a subject (user, role, or all members) reaching the devices selected by Filter as the unix logins listed in Logins, it either grants (Effect allow) or blocks (Effect deny) access. Evaluation is default-deny and deny-first: a matching deny wins over any allow, and access is authorized iff some allow grants it and no deny blocks it.

func NewOwnerAccessPolicy

func NewOwnerAccessPolicy(tenantID, ownerID string) *AccessPolicy

NewOwnerAccessPolicy is the starter policy for the identity access mode: it grants the namespace owner every login on every device. Seeded when a namespace is born identity (creation) or switches to identity with no policies (legacy toggle), so default-deny never locks the owner out while every other member starts with no access.

type ActiveSession

type ActiveSession struct {
	UID      UID       `json:"uid"`
	LastSeen time.Time `json:"last_seen"`
	TenantID string    `json:"tenant_id"`
}

ActiveSession is the liveness half of a session, written on the keep-alive path. It is kept apart from Session so a heartbeat does not rewrite the whole record.

type AuthClaims added in v0.2.0

type AuthClaims struct {
	Claims string `json:"claims"`
}

AuthClaims carries the kind of claims a JWT holds ("user", "device"), which decides how the rest of the token is read.

type Billing added in v0.8.0

type Billing struct {
	// CustomerID is the ID of the customer on the payment gateway.
	CustomerID string `json:"customer_id"`
	// Subscription is the namespace's subscription, or nil when the namespace has a customer but
	// never completed checkout. A nil Subscription is not a failed subscription: the namespace
	// keeps the free tier, exactly as a namespace with no Billing at all does.
	Subscription *BillingSubscription `json:"subscription,omitempty"`
	// CreatedAt is the time at which this billing was created.
	// It follows the RFC 3339 format, and it is empty on records written before the store began
	// to maintain it.
	CreatedAt string `json:"created_at,omitempty"`
	// UpdatedAt is the time at which this billing was last updated.
	// It follows the RFC 3339 format, and it is empty on records written before the store began
	// to maintain it.
	UpdatedAt string `json:"updated_at,omitempty"`
}

Billing contains information about the ShellHub's subscription.

func NewBilling added in v0.12.4

func NewBilling(customerID string) *Billing

NewBilling returns a billing record for a namespace that has a customer on the payment gateway but no subscription yet. The store stamps CreatedAt and UpdatedAt when it writes the record.

func (*Billing) ClearSubscription

func (b *Billing) ClearSubscription()

ClearSubscription detaches the subscription, returning the namespace to the free tier.

func (*Billing) Clone

func (b *Billing) Clone() *Billing

Clone returns a deep copy, so that a caller can build the next billing state without touching the one the namespace still holds.

func (*Billing) HasCustomer

func (b *Billing) HasCustomer() bool

HasCustomer reports whether the namespace is known to the payment provider. A namespace can have a customer and no subscription — that is a namespace that once paid, or is about to.

func (*Billing) HasSubscription added in v0.12.4

func (b *Billing) HasSubscription() bool

HasSubscription reports whether a subscription is attached, saying nothing about whether it is paid up. Use IsActive for that.

func (*Billing) IsActive added in v0.12.4

func (b *Billing) IsActive() bool

IsActive indicates whether the namespace has a subscription that grants full service.

func (*Billing) IsNil added in v0.12.4

func (b *Billing) IsNil() bool

IsNil reports whether the receiver is nil, so a caller holding a *Billing can ask without a nil check of its own. The other predicates here are nil-safe for the same reason.

func (*Billing) SetCustomer added in v0.12.4

func (b *Billing) SetCustomer(id string)

SetCustomer records the payment provider's customer id. It panics on a nil receiver, unlike the predicates above.

func (*Billing) SetSubscription added in v0.12.4

func (b *Billing) SetSubscription(id string, status BillingStatus, currentPeriodEnd int64)

SetSubscription attaches a subscription to the billing, replacing any subscription already there.

func (*Billing) SetSubscriptionStatus

func (b *Billing) SetSubscriptionStatus(status BillingStatus)

SetSubscriptionStatus updates the status of the subscription, and does nothing when the billing has no subscription.

type BillingBlockReason

type BillingBlockReason string

BillingBlockReason names why a billing evaluation denies acceptance or connection.

const (
	// BillingBlockedQuota means the namespace uses every device its plan allows.
	BillingBlockedQuota BillingBlockReason = "quota"
	// BillingBlockedSubscription means the namespace's subscription denies the operation,
	// whatever the device count is.
	BillingBlockedSubscription BillingBlockReason = "subscription"
)

type BillingEvaluation added in v0.12.4

type BillingEvaluation struct {
	// CanAccept indicates if the namespace can accept a new device.
	CanAccept bool `json:"can_accept"`
	// CanConnect indicates if the namespace can create a new connection SSH.
	CanConnect bool `json:"can_connect"`
	// Blocked names what denies the operation, and is empty when nothing does. The two reasons
	// need different answers from the user, so a caller must report them as different errors.
	Blocked BillingBlockReason `json:"blocked,omitempty"`
}

BillingEvaluation contains information about the billing evaluation of acceptance and connection. It is used to evaluate if a device can be accepted or a connection SSH can be created. Its idea is simplify the check the state of the namespace when related to billing.

type BillingStatus added in v0.12.4

type BillingStatus string

BillingStatus represents the status of a subscription on the payment gateway.

https://stripe.com/docs/api/subscriptions/object#subscription_object-status https://stripe.com/docs/billing/subscriptions/overview#subscription-lifecycle

const (
	// BillingStatusActive represents active status without any issues.
	BillingStatusActive BillingStatus = "active"
	// BillingStatusTrialing represents active status without any issues, but the subscription is in trial period.
	BillingStatusTrialing BillingStatus = "trialing"
	// BillingStatusIncomplete represents incomplete status.
	// If the initial payment attempt fails, the status of the subscription becomes incomplete.
	// If payment fails because of a card error, such as a decline, the status of the PaymentIntent is
	// requires_card and the subscription is incomplete.
	BillingStatusIncomplete BillingStatus = "incomplete"
	// BillingStatusIncompleteExpired represents incomplete_expired status.
	// If the first invoice is not paid within 23 hours, the status of the subscription becomes incomplete_expired.
	BillingStatusIncompleteExpired BillingStatus = "incomplete_expired"
	// BillingStatusPastDue represents past_due status.
	// The subscription’s status remains active as long as automatic payments succeed. If automatic payment fails, the
	// subscription updates to past_due and Stripe attempts to recover payment based on your retry rules. If payment
	// recovery fails, you can set the subscription status to canceled, unpaid, or leave it past_due.
	BillingStatusPastDue BillingStatus = "past_due"
	// BillingStatusCanceled represents canceled status.
	BillingStatusCanceled BillingStatus = "canceled"
	// BillingStatusUnpaid represents unpaid status.
	// If the retry attempts are exhausted, the status of the subscription becomes unpaid, depending on your subscriptions settings.
	BillingStatusUnpaid BillingStatus = "unpaid"
	// BillingStatusPaused represents paused status.
	BillingStatusPaused BillingStatus = "paused"
	// BillingStatusToCancelAtEndOfPeriod represents to_cancel_at_end_of_period status.
	// BillingStatusToCancelAtEndOfPeriod is not a Stripe status, but a custom status used by this package to indicate that the subscription is set to cancel at the end of the period.
	BillingStatusToCancelAtEndOfPeriod BillingStatus = "to_cancel_at_end_of_period"
)

Represents the possible statuses of a subscription.

There is no "no subscription" status: a namespace that never completed checkout carries a Billing with a nil Subscription instead.

func (BillingStatus) IsActive added in v0.12.4

func (s BillingStatus) IsActive() bool

IsActive returns true if the subscription grants full service.

`past_due` counts as active because Stripe is still retrying the payment, and `to_cancel_at_end_of_period` still has a paid period left to run.

type BillingSubscription

type BillingSubscription struct {
	// ID is the ID of the subscription on the payment gateway.
	ID string `json:"id"`
	// Status is the current status of the subscription.
	Status BillingStatus `json:"status"`
	// CurrentPeriodEnd is the end of the current period.
	CurrentPeriodEnd int64 `json:"current_period_end"`
}

BillingSubscription is the namespace's subscription on the payment gateway.

type Decision

type Decision struct {
	Allowed bool
	// RequireReauth is set when access is allowed by a policy that carries the
	// re-auth flag; the gateway must run a fresh re-authentication before
	// proceeding, subject to ReauthPeriod.
	RequireReauth bool
	// ReauthPeriod is the matched policy's freshness window in seconds (nil/0 =
	// always). The gateway skips the re-auth when the identity re-authed within
	// it. Only meaningful when RequireReauth is set.
	ReauthPeriod *int

	// Reason is the cause of the refusal, empty when Allowed.
	Reason DenialReason
	// PolicyName is the policy that produced the refusal. Set for
	// ReasonDeniedByPolicy and ReasonPolicyUnevaluable only.
	PolicyName string
	// Login is the login the request asked for. Set for ReasonNoGrant only.
	Login string
}

Decision is the outcome of an Access Policy authorization check.

func (Decision) Message

func (d Decision) Message() string

Message renders a refusal as an operator-facing sentence for the log. It is presentation: the reason code and its fields are the record, and callers must branch on Reason rather than parse this.

type DenialReason

type DenialReason string

DenialReason is the cause of a denied Decision, as a stable value operators can count and filter on. The human sentence is derived from it (see Decision.Message), never the other way round, so rewording never breaks a query.

const (
	// ReasonNotAMember refuses because the user does not belong to the device's
	// namespace, so no policy in it could ever apply to them.
	ReasonNotAMember DenialReason = "not_a_member"
	// ReasonDeniedByPolicy refuses because a deny policy matched. Carries PolicyName.
	ReasonDeniedByPolicy DenialReason = "denied_by_policy"
	// ReasonPolicyUnevaluable refuses because a deny policy could not be evaluated,
	// and evaluation fails closed. Carries PolicyName.
	ReasonPolicyUnevaluable DenialReason = "policy_unevaluable"
	// ReasonNoGrant refuses because no allow policy grants the requested login on
	// the device, leaving default-deny to stand. Carries Login.
	ReasonNoGrant DenialReason = "no_grant"
)

type Device

type Device struct {
	// UID is the unique identifier for a device.
	UID string `json:"uid"`

	CreatedAt time.Time  `json:"created_at"`
	RemovedAt *time.Time `json:"removed_at"`

	Name      string          `json:"name" validate:"required,device_name"`
	Identity  *DeviceIdentity `json:"identity"`
	Info      *DeviceInfo     `json:"info"`
	PublicKey string          `json:"public_key"`
	TenantID  string          `json:"tenant_id"`

	// LastSeen represents the timestamp of the most recent ping from the device to the server.
	LastSeen time.Time `json:"last_seen"`
	// DisconnectedAt stores the timestamp when the device disconnected from the server.
	// When nil, it indicates the device is potentially online.
	//
	// Due to potential network issues, this field might be nil even when the device
	// is actually offline. For reliable connection status, check both this and
	// [Device.LastSeen] fields.
	DisconnectedAt *time.Time `json:"-"`
	// Online indicates whether the device is currently connected. This field is not
	// persisted to the database but is computed based on both [Device.LastSeen] and
	// [Device.DisconnectedAt] fields to determine the current connection status.
	Online bool `json:"online"`

	Namespace       string          `json:"namespace"`
	Status          DeviceStatus    `json:"status" validate:"oneof=accepted rejected pending unused"`
	StatusUpdatedAt time.Time       `json:"status_updated_at"`
	RemoteAddr      string          `json:"remote_addr"`
	Position        *DevicePosition `json:"position"`
	Acceptable      bool            `json:"acceptable"`

	CustomFields map[string]string `json:"custom_fields,omitempty"`

	// Ephemeral reports whether the device was enrolled with an ephemeral install key and should be
	// removed automatically once it stays offline past EphemeralTimeout.
	Ephemeral bool `json:"ephemeral"`
	// EphemeralTimeout is how many minutes the device may stay offline before removal, copied from
	// the install key at enrollment. Only meaningful when Ephemeral is true.
	EphemeralTimeout int `json:"ephemeral_timeout,omitempty"`
	// InstallKeyID is the digest of the install key the device enrolled with (a real key or the
	// namespace's legacy key). It attributes the device to its enrollment source.
	InstallKeyID string `json:"install_key_id,omitempty"`
	// LastEnrollmentAttemptAt is when the enrollment policy was last (re-)evaluated for the device. It
	// throttles reconciliation of a still-pending enrollment on the agent's periodic AuthDevice. Nil
	// until the first re-evaluation.
	LastEnrollmentAttemptAt *time.Time `json:"last_enrollment_attempt_at,omitempty"`

	Taggable `json:",inline"`
}

Device is an enrolled machine. Its UID is derived from the identity the agent presents, so two agents presenting the same identity are the same device rather than two of them.

type DeviceAuth

type DeviceAuth struct {
	Hostname  string          `json:"hostname,omitempty" validate:"required_without=Identity,omitempty,hostname_rfc1123" hash:"-"`
	Identity  *DeviceIdentity `json:"identity,omitempty" validate:"required_without=Hostname,omitempty"`
	PublicKey string          `json:"public_key"`
	TenantID  string          `json:"tenant_id"`
	// InstallKey is an optional install key presented at install time to auto-accept the device. It is
	// excluded from the UID hash so it never changes a device's identity.
	InstallKey string `json:"install_key,omitempty" hash:"-"`
}

DeviceAuth is the part of an authentication request the device's UID is hashed from. A field tagged hash:"-" is deliberately outside that hash: changing it must not give the agent a new identity.

type DeviceAuthRequest

type DeviceAuthRequest struct {
	Info     *DeviceInfo `json:"info"`
	Sessions []string    `json:"sessions,omitempty"`
	*DeviceAuth
}

DeviceAuthRequest is what an agent sends to enroll or to re-authenticate. It repeats on every agent restart, so handling it must be idempotent.

type DeviceAuthResponse

type DeviceAuthResponse struct {
	UID       string `json:"uid"`
	Token     string `json:"token"`
	Name      string `json:"name"`
	Namespace string `json:"namespace"`
	// TenantID is the namespace the device was enrolled into. An agent that authenticated with an
	// install key alone learns its namespace here, having had no tenant to send. Additive and
	// optional: older agents that don't read it are unaffected.
	TenantID string `json:"tenant_id,omitempty"`
	// Status is the device's enrollment status after this auth (accepted/pending/rejected). It lets a
	// current agent react to its authorization state (e.g. stop opening the tunnel when not accepted)
	// instead of connecting blind. Additive and optional: older agents that don't read it are
	// unaffected.
	Status DeviceStatus `json:"status,omitempty"`
	// Config holds device-specific configuration settings.
	// This can include various parameters that the device needs to operate correctly.
	// The structure of this map can vary depending on the device type and its requirements.
	// Example configurations might include network settings, operational modes, or feature toggles.
	// It's designed to be flexible to accommodate different device needs.
	Config map[string]any `json:"config,omitempty"`
}

DeviceAuthResponse is what an agent receives on a successful authentication: the token it authenticates with from then on, and the identity the server assigned it.

type DeviceAuthStatus

type DeviceAuthStatus struct {
	Status DeviceStatus `json:"status"`
}

DeviceAuthStatus is the device's current status as reported to the device itself while it waits for acceptance.

type DeviceConflicts added in v0.19.0

type DeviceConflicts struct {
	Name string
}

DeviceConflicts holds user attributes that must be unique for each itam and can be utilized in queries to identify conflicts.

func (*DeviceConflicts) Distinct added in v0.19.0

func (c *DeviceConflicts) Distinct(device *Device)

Distinct removes the c's attributes whether it's equal to the device attribute.

type DeviceIdentity added in v0.2.1

type DeviceIdentity struct {
	MAC string `json:"mac"`
}

DeviceIdentity is the hardware identity an agent claims. It feeds the device's UID, so a machine that keeps its MAC keeps its device across reinstalls.

type DeviceInfo added in v0.2.1

type DeviceInfo struct {
	ID         string `json:"id"`
	PrettyName string `json:"pretty_name"`
	Version    string `json:"version"`
	Arch       string `json:"arch"`
	Platform   string `json:"platform"`
}

DeviceInfo is what the agent reports about the operating system it runs on. It is descriptive only: nothing authorizes on it, and the agent is free to change it between authentications.

type DeviceLoginCode

type DeviceLoginCode struct {
	Code      string `json:"code"`
	ExpiresIn int    `json:"expires_in_seconds"`
}

DeviceLoginCode is a short-lived code that deep-links a pending device into the console's accept page. It carries no authority by itself: accepting the device still requires an authenticated user with the DeviceAccept permission in the device's namespace.

type DeviceLoginCodePreview

type DeviceLoginCodePreview struct {
	Kind      string          `json:"kind"`
	UID       string          `json:"uid,omitempty"`
	Name      string          `json:"name"`
	Identity  *DeviceIdentity `json:"identity"`
	Info      *DeviceInfo     `json:"info"`
	Namespace string          `json:"namespace,omitempty"`
	TenantID  string          `json:"tenant_id,omitempty"`
	Status    DeviceStatus    `json:"status,omitempty"`
}

DeviceLoginCodePreview is what an authenticated user sees when resolving a device login code before accepting the device. For pairing codes the device does not exist yet, so UID, Namespace, TenantID and Status are empty.

type DevicePairing

type DevicePairing struct {
	Code      string       `json:"code,omitempty"`
	ExpiresIn int          `json:"expires_in_seconds,omitempty"`
	Status    DeviceStatus `json:"status"`
	TenantID  string       `json:"tenant_id,omitempty"`
}

DevicePairing is the response to a pairing creation request. When the device (identified by its public key) was already accepted into a namespace, the server resolves it immediately: Status is "accepted" and TenantID is set, so the agent learns its tenant without waiting on a code. Otherwise a Code is returned to poll.

type DevicePairingAccepted

type DevicePairingAccepted struct {
	UID       string `json:"uid"`
	TenantID  string `json:"tenant_id"`
	Namespace string `json:"namespace"`
}

DevicePairingAccepted is the response to a pairing accept request.

type DevicePairingRequest

type DevicePairingRequest struct {
	Hostname  string          `json:"hostname,omitempty"`
	Identity  *DeviceIdentity `json:"identity,omitempty"`
	Info      *DeviceInfo     `json:"info"`
	PublicKey string          `json:"public_key"`
	Code      string          `json:"code,omitempty"`
}

DevicePairingRequest is the identity payload a tenant-less agent submits to start a pairing. It mirrors the fields of a device auth request minus the tenant, which the user chooses at accept time.

Code carries a pre-authorized pairing code the agent was given at install time. When set, the server claims it and accepts the device into the pre-authorized namespace instead of returning a code to poll.

type DevicePairingStatus

type DevicePairingStatus struct {
	Status   DeviceStatus `json:"status"`
	TenantID string       `json:"tenant_id,omitempty"`
	UID      string       `json:"uid,omitempty"`
	Name     string       `json:"name,omitempty"`
}

DevicePairingStatus is what a tenant-less agent — or the console page that minted a pre-authorized code — polls while waiting for the device to be accepted. TenantID is set once accepted; UID and Name identify the resulting device so the console can link straight to it.

type DevicePosition added in v0.8.0

type DevicePosition struct {
	Latitude  float64 `json:"latitude"`
	Longitude float64 `json:"longitude"`
}

DevicePosition is the device's geolocation, resolved from the address it connected from. It is a guess from a GeoIP database, not something the device reports.

type DeviceStatus added in v0.11.8

type DeviceStatus string

DeviceStatus is where a device sits in the enrollment lifecycle. It decides whether the device may open a tunnel and whether it counts against the namespace's device limit.

const (
	// DeviceStatusAccepted is a device allowed to connect. Only accepted devices count against a
	// namespace's device limit.
	DeviceStatusAccepted DeviceStatus = "accepted"
	// DeviceStatusPending is a device that has authenticated but is waiting for a member to accept it.
	DeviceStatusPending DeviceStatus = "pending"
	// DeviceStatusRejected is a device a member turned away. It may re-authenticate, and stays
	// rejected until someone changes that.
	DeviceStatusRejected DeviceStatus = "rejected"
	// DeviceStatusRemoved is a device deleted from the namespace. It frees its slot against the
	// device limit immediately, and the row is kept so the same machine returning is recognised.
	DeviceStatusRemoved DeviceStatus = "removed"
	// DeviceStatusUnused is an accepted device that has never connected.
	DeviceStatusUnused DeviceStatus = "unused"
	// DeviceStatusEmpty is the zero value, which matches every status in a query rather than none.
	DeviceStatusEmpty DeviceStatus = ""
)

type DeviceTag added in v0.14.0

type DeviceTag struct {
	Tag string `validate:"required,min=3,max=255,alphanum,ascii,excludes=/@&:"`
}

DeviceTag is a single tag as it is validated, which is where the character restrictions live — a tag ends up in an SSH address, so "/@&:" would make one ambiguous.

func NewDeviceTag added in v0.14.0

func NewDeviceTag(tag string) DeviceTag

NewDeviceTag wraps a raw tag for validation. It does not validate on its own: pass the result to the validator.

type Endpoints

type Endpoints struct {
	API string `json:"api"`
	SSH string `json:"ssh"`
}

Endpoints are the addresses a client connects back on, as the server sees itself from outside — behind a reverse proxy they come from configuration, not from the request.

type Filter added in v0.3.0

type Filter struct {
	// Type os the filter. Type can be "property" or "operator". When Type is "property", the Params field must is set
	// to PropertyParams structure and when set "operator", the Params field must be set to OperatorParams structure.
	Type string `json:"type,omitempty"`
	// Params is the filter params. Params can be either PropertyParams or OperatorParams.
	Params any `json:"params,omitempty"`
}

Filter is one node of a query filter as it arrives from a client. It is a tagged union: Type picks which shape Params holds, and UnmarshalJSON is what enforces the pairing — build one by unmarshalling rather than by hand, or Params ends up a map instead of a params struct.

func (*Filter) UnmarshalJSON added in v0.3.2

func (f *Filter) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes Params into the struct Type names. An unrecognized Type is not an error: it leaves Params nil, so a filter the server does not understand narrows nothing rather than failing the request.

type FirewallConnection

type FirewallConnection struct {
	// Namespace is the namespace name, not its tenant ID.
	Namespace string `json:"namespace"`
	// Hostname is the device name within the namespace.
	Hostname string `json:"hostname"`
	// Username is the user being requested on the device, not the ShellHub user.
	Username  string `json:"username"`
	IPAddress string `json:"ip_address"`
}

FirewallConnection describes the SSH connection attempt a firewall rule matches against.

type ID added in v0.5.0

type ID struct {
	ID string
}

ID carries the authenticated user's id as the API's gateway reads it out of a request's context. It is a struct rather than a string so a missing id is a nil pointer, not "".

type Info added in v0.2.1

type Info struct {
	Version   string    `json:"version"`
	Endpoints Endpoints `json:"endpoints"`
}

Info is what an unauthenticated client reads to learn the server's version and where to reach it, so it is the one payload that must stay stable across upgrades.

type InstallKey

type InstallKey struct {
	// ID is the unique identifier of the install key: the SHA256 digest of the plaintext key. The
	// plaintext itself is never returned, but the digest is safe to expose (it can't be reversed) and
	// lets a device's install_key_id be matched back to its key.
	ID string `json:"id"`
	// Name is an external identifier. It is unique per tenant ID, not globally.
	Name string `json:"name"`
	// TenantID is the install key's namespace ID.
	TenantID string `json:"tenant_id"`
	// Mode is the enrollment policy applied to devices that enroll with the key.
	Mode InstallKeyMode `json:"mode"`
	// WebhookURL is the integrator endpoint called at enrollment when Mode is webhook.
	WebhookURL string `json:"webhook_url"`
	// WebhookSecret signs the webhook request (HMAC-SHA256) so the integrator can trust it. It is
	// internal-only and never serialized to clients.
	WebhookSecret string `json:"-"`
	// AllowedMACs is the set of device MACs accepted when Mode is allowlist. Any MAC outside it is
	// rejected.
	AllowedMACs []string `json:"allowed_macs"`
	// WebhookTimeout is how long (seconds) the synchronous webhook call may take before failing closed
	// to pending. Zero means the default.
	WebhookTimeout int `json:"webhook_timeout"`
	// WebhookCallbackTTL is how long (seconds) the deferred-decision callback token stays valid. Zero
	// means the default.
	WebhookCallbackTTL int `json:"webhook_callback_ttl"`
	// Reusable reports whether the key may enroll more than one device.
	Reusable bool `json:"reusable"`
	// UsageLimit caps how many devices may enroll with the key. Zero means unlimited.
	UsageLimit int `json:"usage_limit"`
	// UsedTimes is how many devices have enrolled with the key.
	UsedTimes int `json:"used_times"`
	// LastUsedAt is when a device last enrolled with the key.
	LastUsedAt *time.Time `json:"last_used_at"`
	// Ephemeral marks devices enrolled with the key for automatic removal once offline past
	// EphemeralTimeout.
	Ephemeral bool `json:"ephemeral"`
	// EphemeralTimeout is how many minutes an ephemeral device may stay offline before removal
	// (1-10). Only meaningful when Ephemeral is true.
	EphemeralTimeout int `json:"ephemeral_timeout"`
	// Tags are the names of the namespace tags applied to devices enrolled with the key.
	Tags []string `json:"tags"`
	// Revoked reports whether the key has been permanently revoked. Revocation is one-way: a revoked
	// key can never enroll again. For a reversible pause, use Disabled instead.
	Revoked bool `json:"revoked"`
	// Disabled reports whether the key is temporarily paused. Unlike Revoked, it is reversible: a
	// disabled key stops enrolling but can be re-enabled at any time.
	Disabled bool `json:"disabled"`
	// Type discriminates the key's origin: a user-created key, or one of the namespace's two
	// auto-managed system keys (legacy, pairing). System keys are always valid and are not presentable
	// by an agent; see IsSystem.
	Type InstallKeyType `json:"type"`
	// KeyEncrypted holds the plaintext key encrypted at rest (AES-GCM), so an admin can reveal it
	// later. It is internal-only and never serialized to clients (reveal returns the decrypted value
	// through its own endpoint).
	KeyEncrypted string `json:"-"`
	// KeyHint is a short, non-secret prefix of the plaintext key, used to render a recognizable
	// masked fingerprint in the list without exposing the secret.
	KeyHint string `json:"key_hint"`
	// CreatedBy is the ID of the user who created the key.
	CreatedBy string `json:"created_by"`
	// CreatedAt is the creation date of the key.
	CreatedAt time.Time `json:"created_at"`
	// UpdatedAt is the last update date of the key.
	UpdatedAt time.Time `json:"updated_at"`
	// ExpiresAt is the absolute date the key expires. A nil value means the key never expires.
	ExpiresAt *time.Time `json:"expires_at"`
}

InstallKey is a reusable, revocable, namespace-scoped credential that decides how a device is enrolled. Its InstallKey.Mode is the policy: a device enrolling with the key lands accepted, pending, or rejected according to the mode. The device inherits the key's tags and is marked ephemeral when the key is.

The plaintext key is returned only once, at creation. Only its SHA256 hash is stored, so the key cannot be recovered afterwards. Use InstallKey.IsValid to verify a key can still enroll.

func (*InstallKey) IsPairing

func (s *InstallKey) IsPairing() bool

IsPairing reports whether this is the namespace's auto-managed pairing key: the source attributed to devices accepted through the tenant-less pairing-code flow.

func (*InstallKey) IsSystem

func (s *InstallKey) IsSystem() bool

IsSystem reports whether this is one of the namespace's auto-managed system keys (legacy or pairing), as opposed to a user-created key. Checked positively (not `!= user`) so a zero-valued Type — an in-memory key built before persistence defaults it to user — reads as a user key.

func (*InstallKey) IsValid

func (s *InstallKey) IsValid() bool

IsValid reports whether the install key can still enroll a device: it must not be revoked, disabled, expired, or overused.

func (*InstallKey) ReconcilableOnAuth

func (s *InstallKey) ReconcilableOnAuth() bool

ReconcilableOnAuth reports whether a still-pending device enrolled with this key should have its enrollment policy re-evaluated on a later AuthDevice. Only webhook and allowlist can leave a device pending on a recoverable condition (a deferred/failed integrator, or an accept blocked by the license limit), so only those are retried; automatic/manual have no such recoverable pending state.

func (*InstallKey) WebhookCallbackTTLOrDefault

func (s *InstallKey) WebhookCallbackTTLOrDefault() int

WebhookCallbackTTLOrDefault returns the deferred-decision token TTL in seconds, clamped and defaulted.

func (*InstallKey) WebhookTimeoutOrDefault

func (s *InstallKey) WebhookTimeoutOrDefault() int

WebhookTimeoutOrDefault returns the synchronous webhook timeout in seconds, clamped to the allowed range and defaulted when unset.

type InstallKeyConflicts

type InstallKeyConflicts struct {
	ID   string
	Name string
}

InstallKeyConflicts holds install key attributes that must be unique per tenant ID and can be used in queries to identify conflicts.

type InstallKeyEvent

type InstallKeyEvent struct {
	// ID is the unique identifier of the event.
	ID string `json:"id"`
	// InstallKeyID is the digest of the install key the device enrolled with.
	InstallKeyID string `json:"install_key_id"`
	// TenantID is the enrolling device's namespace ID.
	TenantID string `json:"tenant_id"`
	// DeviceUID is the enrolled device's UID at enrollment time.
	DeviceUID string `json:"device_uid"`
	// Hostname is the enrolled device's hostname at enrollment time.
	Hostname string `json:"hostname"`
	// MAC is the enrolled device's MAC at enrollment time. It may be empty.
	MAC string `json:"mac"`
	// Info is the enrolled device's system info at enrollment time. It may be nil.
	Info *DeviceInfo `json:"info"`
	// SourceIP is the device's remote address at enrollment time. It may be empty (a pairing accept
	// materializes the device without an IP).
	SourceIP string `json:"source_ip"`
	// PublicKey is the enrolled device's public key (PEM) at enrollment time. It identifies the exact
	// credential: re-keying yields a new key here (and a new device), so it tells re-keyed enrollments
	// apart. May be empty for events recorded before this was captured.
	PublicKey string `json:"public_key,omitempty"`
	// Fingerprint is the SHA256 fingerprint of PublicKey, computed at read time (not stored). Empty when
	// PublicKey is absent or unparseable.
	Fingerprint string `json:"fingerprint,omitempty"`
	// Ephemeral reports whether the key marked the device ephemeral. Ephemeral enrollments are kept in
	// the history (audit completeness) so the UI can mark or filter them rather than drop them.
	Ephemeral bool `json:"ephemeral"`
	// ReRegistration reports whether this was a re-registration of a previously removed device rather
	// than a first registration.
	ReRegistration bool `json:"re_registration"`
	// Timestamp is when the enrollment was recorded.
	Timestamp time.Time `json:"timestamp"`
	// DeviceStatus is the enrolled device's *current* status (accepted/pending/rejected), joined live
	// at list time so the history can offer an accept/reject action. It is empty when the device no
	// longer exists (hard-deleted). It is not stored on the event row.
	DeviceStatus DeviceStatus `json:"device_status"`
	// DecidedStatus and DecidedAt freeze the enrollment's outcome on the event: the terminal status
	// (accepted/rejected) and when it was set. They are stamped once, when the device is accepted or
	// rejected, so the audit survives the device being removed (the live status can't). Nil/empty while
	// the enrollment is still pending.
	DecidedStatus DeviceStatus `json:"decided_status,omitempty"`
	DecidedAt     *time.Time   `json:"decided_at,omitempty"`
	// IsCurrent reports whether this is the device's newest enrollment event. A device removed and
	// re-registered shares one device row across several events, so the live status/action belongs to
	// the newest one alone; older events are historical. Computed at read time; drives the accept/reject
	// action only (the decision itself is frozen per-event above).
	IsCurrent bool `json:"is_current"`
}

InstallKeyEvent is one row in an install key's append-only enrollment history: it records a single device enrolling with the key. The device facts are captured and denormalized at enrollment time so the audit survives a later device rename or removal (including ephemeral devices, which are auto-removed). The enrollment facts are immutable; the outcome (DecidedStatus/DecidedAt) is stamped once when the device is accepted/rejected. Rows are never deleted by the application.

type InstallKeyMode

type InstallKeyMode string

InstallKeyMode is the per-key enrollment policy: it decides a device's initial status when the device enrolls with the key.

const (
	// InstallKeyModeAutomatic accepts the device on enrollment (the classic install-key behavior).
	InstallKeyModeAutomatic InstallKeyMode = "automatic"
	// InstallKeyModeManual lands the device pending for manual review. The legacy/system key is always
	// this mode.
	InstallKeyModeManual InstallKeyMode = "manual"
	// InstallKeyModeWebhook defers the decision to an integrator's endpoint, called at enrollment.
	InstallKeyModeWebhook InstallKeyMode = "webhook"
	// InstallKeyModeAllowlist accepts the device when its MAC is in AllowedMACs, otherwise rejects it.
	InstallKeyModeAllowlist InstallKeyMode = "allowlist"
)

type InstallKeyType

type InstallKeyType string

InstallKeyType discriminates a key's origin: a user-created key, or one of the two auto-managed system keys every namespace has. The system types are told apart by this field (not by name), and neither is presentable by an agent nor freely editable by a user.

const (
	// InstallKeyTypeUser is a normal user-created key.
	InstallKeyTypeUser InstallKeyType = "user"
	// InstallKeyTypeLegacy is the tenant-only keyless enrollment source (a device presenting only a
	// tenant ID, no install key). Manual mode: such devices land pending.
	InstallKeyTypeLegacy InstallKeyType = "legacy"
	// InstallKeyTypePairing is the code-pairing enrollment source (a tenant-less agent accepted via its
	// printed code). Devices accepted through the pairing flow attribute here, not to the legacy key.
	InstallKeyTypePairing InstallKeyType = "pairing"
)

type InstanceAPIKey

type InstanceAPIKey struct {
	// ID is the unique identifier of the key. It is a SHA256 hash of the prefixed plaintext key.
	ID string `json:"-"`
	// Name is an external identifier for a given key. It is unique across the instance.
	Name string `json:"name"`
	// CreatedBy is the ID of the instance administrator who created the key.
	CreatedBy string `json:"created_by"`
	// CreatedAt is the creation date of the key.
	CreatedAt time.Time `json:"created_at"`
	// UpdatedAt is the last update date of the key.
	UpdatedAt time.Time `json:"updated_at"`
	// ExpiresAt is the expiration date of the key. Unlike [APIKey], it is always set: an instance
	// key cannot be created without one.
	ExpiresAt time.Time `json:"expires_at"`
}

InstanceAPIKey authenticates a request as an instance administrator. Unlike APIKey it carries neither a namespace nor a role, and it is honoured only on the admin surface.

The ID is a SHA256 digest of the plaintext key and is never returned to the end user, so a key is identified externally by its name alone. A key stops authenticating when it expires or when the user in CreatedBy is no longer an instance administrator; use InstanceAPIKey.IsValid for the former.

func (*InstanceAPIKey) IsValid

func (a *InstanceAPIKey) IsValid() bool

IsValid reports whether the key has not expired yet.

type Member added in v0.5.0

type Member struct {
	ID      string          `json:"id,omitempty"`
	AddedAt time.Time       `json:"added_at"`
	Email   string          `json:"email" validate:"email"`
	Role    authorizer.Role `json:"role" validate:"required,oneof=administrator operator observer"`
	// Type mirrors the member's user account type (human or service). It is denormalized from
	// the joined users row so authorization can exclude service accounts from human-oriented
	// policy subjects (e.g. all-members) without a second query. Empty for legacy rows loaded
	// without the users join; treat empty as human.
	Type UserType `json:"type,omitempty"`
	// AccountStatus is the member's underlying user account status (confirmed or
	// not-confirmed). A not-confirmed member still has to finish setting up their account. It
	// is the account status, not the membership-invitation status (accepted/pending), which is
	// a separate concept.
	AccountStatus UserStatus `json:"account_status,omitempty"`
	// AwaitingApproval mirrors the member's user account flag: true while a namespace admin
	// provisioned them but a system admin has not approved the account yet. The account cannot
	// sign in until an admin approves it.
	AwaitingApproval bool `json:"awaiting_approval,omitempty"`
}

Member ties a user to a namespace with a role. It is the only thing that grants a user access to a namespace, and it denormalizes enough of the user row that authorization needs no second query.

type MemberView

type MemberView struct {
	ID       string          `json:"id,omitempty"`
	Name     string          `json:"name,omitempty"`
	Username string          `json:"username,omitempty"`
	Email    string          `json:"email"`
	Role     authorizer.Role `json:"role"`
	// Status is MemberStatusActive or MemberStatusAwaitingApproval.
	Status  string    `json:"status"`
	AddedAt time.Time `json:"added_at"`
}

MemberView is the enriched, list-friendly member representation returned by GET /api/namespaces/members. Unlike Member it carries the user's name/username and a flattened account Status, joining the users table.

type MembershipInvitation added in v0.21.4

type MembershipInvitation struct {
	ID              string                     `json:"-"`
	TenantID        string                     `json:"-"`
	UserID          string                     `json:"-"`
	InvitedBy       string                     `json:"invited_by"`
	CreatedAt       time.Time                  `json:"created_at"`
	UpdatedAt       time.Time                  `json:"updated_at"`
	ExpiresAt       *time.Time                 `json:"expires_at"`
	Status          MembershipInvitationStatus `json:"status"`
	StatusUpdatedAt time.Time                  `json:"status_updated_at"`
	Role            authorizer.Role            `json:"role"`
	Invitations     int                        `json:"-"`
	// Sig is the one-time signature that ties the invitation link to this row. It
	// replaces the former Redis "invite={sig}" token; validity is the row's ExpiresAt.
	Sig string `json:"-"`

	// NamespaceName isn't saved on the database
	NamespaceName string `json:"-"`
	// UserEmail isn't saved on the database
	UserEmail string `json:"-"`
}

MembershipInvitation is a pending offer of membership in a namespace. It becomes a Member only when accepted; until then it grants nothing. Sig is what makes the invitation link usable, so it is never serialized.

func (MembershipInvitation) IsExpired added in v0.21.4

func (m MembershipInvitation) IsExpired() bool

IsExpired reports whether the invitation is past its deadline. An invitation with no ExpiresAt never expires.

func (MembershipInvitation) IsPending added in v0.21.4

func (m MembershipInvitation) IsPending() bool

IsPending reports whether the invitation is unanswered, which is not the same as usable — an expired invitation is still pending.

type MembershipInvitationNotification

type MembershipInvitationNotification struct {
	// Signature is the invitation's one-time signature; the accept-invite link is keyed by it.
	Signature string `json:"signature"`
	// ExpiresAt is when the invitation stops resolving, shown to the recipient as the link expiry.
	ExpiresAt time.Time `json:"expires_at"`
	// RecipientEmail is the invited address, already lowercased.
	RecipientEmail string `json:"recipient_email"`
	// RecipientName is the invitee's display name, empty for a not-yet-registered invitee (exactly
	// as before).
	RecipientName string `json:"recipient_name"`
	// ForwardedProto and ForwardedHost come from the originating request's X-Forwarded-* headers and
	// build the accept-invite link in the email.
	ForwardedProto string `json:"forwarded_proto"`
	ForwardedHost  string `json:"forwarded_host"`
}

MembershipInvitationNotification is the typed, email-relevant snapshot of a membership invitation event. It is assembled once by the membership-intake flow and carried — via the OnMembershipInvited hook and the internal client — to the worker that renders and sends the invitation email, which reads it without a single store round-trip.

It is the single contract across the shellhub↔cloud seam: JSON-encoded over the worker's []byte transport, replacing the former positional colon-delimited string. It deliberately carries only what the email template consumes — not the role or namespace name, which the template uses neither of.

type MembershipInvitationStatus added in v0.21.4

type MembershipInvitationStatus string

MembershipInvitationStatus is where an invitation sits. Expiry is not one of the values: an expired invitation stays pending, and IsExpired answers separately.

const (
	// MembershipInvitationStatusPending is an invitation nobody has answered. It is also the status
	// of an expired invitation, so check IsExpired before treating one as open.
	MembershipInvitationStatusPending MembershipInvitationStatus = "pending"
	// MembershipInvitationStatusAccepted is an invitation the invitee took, which is what created
	// their membership.
	MembershipInvitationStatusAccepted MembershipInvitationStatus = "accepted"
	// MembershipInvitationStatusRejected is an invitation the invitee declined.
	MembershipInvitationStatusRejected MembershipInvitationStatus = "rejected"
	// MembershipInvitationStatusCancelled is an invitation the namespace withdrew before it was
	// answered.
	MembershipInvitationStatusCancelled MembershipInvitationStatus = "cancelled"
)

type Namespace added in v0.5.0

type Namespace struct {
	Name     string             `json:"name"  validate:"required,hostname_rfc1123,excludes=.,lowercase"`
	Owner    string             `json:"owner"`
	TenantID string             `json:"tenant_id"`
	Members  []Member           `json:"members"`
	Settings *NamespaceSettings `json:"settings"`
	Devices  int                `json:"-"`

	DevicesAcceptedCount int64 `json:"devices_accepted_count"`
	DevicesPendingCount  int64 `json:"devices_pending_count"`
	DevicesRejectedCount int64 `json:"devices_rejected_count"`
	DevicesRemovedCount  int64 `json:"devices_removed_count"`

	Sessions   int       `json:"-"`
	MaxDevices int       `json:"max_devices"`
	CreatedAt  time.Time `json:"created_at"`
	Billing    *Billing  `json:"billing"`
	Type       Type      `json:"type"`
}

Namespace is a tenant: the unit devices, members and billing all hang off. Its TenantID is what every scoped query filters on, and its Name is what an SSH address resolves through, so the two are equally load-bearing and only the name may change.

func (*Namespace) DeviceLimit

func (n *Namespace) DeviceLimit() NamespaceDeviceLimit

DeviceLimit returns the namespace's ceiling paired with the count it is measured against, so a caller asking both questions reads one consistent snapshot.

func (*Namespace) FindMember added in v0.14.0

func (n *Namespace) FindMember(id string) (*Member, bool)

FindMember checks if a member with the specified ID exists in the namespace.

func (*Namespace) HasMaxDevices added in v0.11.8

func (n *Namespace) HasMaxDevices() bool

HasMaxDevices reports whether the namespace has a finite device ceiling at all. Community instances and unbilled namespaces carry -1, which is no ceiling.

func (*Namespace) HasMaxDevicesReached added in v0.11.8

func (n *Namespace) HasMaxDevicesReached() bool

HasMaxDevicesReached reports whether the ceiling is used up. Ask HasMaxDevices first: with no ceiling this compares against -1 and answers true.

type NamespaceConflicts added in v0.20.1

type NamespaceConflicts struct {
	Name string
}

NamespaceConflicts holds namespace attributes that must be unique for each document and can be utilized in queries to identify conflicts.

func (*NamespaceConflicts) Distinct added in v0.20.1

func (c *NamespaceConflicts) Distinct(namespace *Namespace)

Distinct removes the c attributes whether it's equal to the namespace attribute.

type NamespaceDeviceLimit

type NamespaceDeviceLimit struct {
	MaxDevices           int
	DevicesAcceptedCount int64
}

NamespaceDeviceLimit is a namespace's device ceiling and the accepted-device count it is measured against, carrying the rule so it stays defined once.

func (NamespaceDeviceLimit) HasMax

func (l NamespaceDeviceLimit) HasMax() bool

HasMax reports whether the namespace has a finite device ceiling.

Generally, a namespace has a MaxDevices value greater than 0 when the ShellHub is either in community version or the namespace does not have a billing plan enabled, because, in this case, we set this value to -1.

func (NamespaceDeviceLimit) IsReached

func (l NamespaceDeviceLimit) IsReached() bool

IsReached reports whether the accepted-device count has caught up with the ceiling. Only counts accepted devices. Removed devices no longer count towards the limit, allowing immediate slot reuse after deletion.

type NamespaceSettings added in v0.5.0

type NamespaceSettings struct {
	SessionRecord          bool   `json:"session_record"`
	ConnectionAnnouncement string `json:"connection_announcement"`
	// SSHAccessMode selects the SSH authorization model for the namespace. In
	// "identity" mode every SSH login is gated on an out-of-band browser approval
	// (no device credential required) and governed by Access Policies; the legacy
	// key ACL and firewall checks are bypassed. "legacy" keeps the key/firewall
	// behavior unchanged. New namespaces are born "identity"; namespaces that
	// predate identity-first default to "legacy".
	SSHAccessMode string `json:"ssh_access_mode"`
	// SSHLegacyAllowed marks a namespace that predates identity-first
	// (grandfathered): only these may switch the SSH access mode back to legacy.
	// Namespaces born identity have it false and can never leave identity mode.
	SSHLegacyAllowed bool `json:"ssh_legacy_allowed"`
}

NamespaceSettings is the per-namespace policy the SSH gateway reads on every connection. A nil Settings on a Namespace means the defaults, not "everything off".

func (*NamespaceSettings) IsIdentityAccess

func (s *NamespaceSettings) IsIdentityAccess() bool

IsIdentityAccess reports whether the namespace uses the identity-based SSH access mode. It is nil-safe so call sites can use it without a prior guard.

type OperatorParams added in v0.3.2

type OperatorParams struct {
	Name string `json:"name"`
}

OperatorParams joins the filters around it ("and", "or"), so it is what makes a filter list a tree rather than a conjunction.

type PolicyAction

type PolicyAction string

PolicyAction is whether an Access Policy grants access (allow) or blocks it (deny).

const (
	// PolicyActionAllow grants access to the subject; the default.
	PolicyActionAllow PolicyAction = "allow"
	// PolicyActionDeny blocks access. Deny is evaluated before allow and wins
	// over any allow, however specific: it is a subtractive blocklist carved out
	// of the broad grants, not a base layer (default-deny already blocks the rest).
	PolicyActionDeny PolicyAction = "deny"
)

type PolicySubject

type PolicySubject struct {
	Type  PolicySubjectType `json:"type"`
	Value string            `json:"value"`
}

PolicySubject identifies who an Access Policy grants access to.

type PolicySubjectType

type PolicySubjectType string

PolicySubjectType enumerates who an Access Policy grants access to.

const (
	// PolicySubjectUser grants a single user, identified by user id in Value.
	PolicySubjectUser PolicySubjectType = "user"
	// PolicySubjectRole grants every member holding a role, named in Value.
	PolicySubjectRole PolicySubjectType = "role"
	// PolicySubjectAllMembers grants every member of the namespace; Value is empty.
	PolicySubjectAllMembers PolicySubjectType = "all-members"
)

type PrivateKey added in v0.5.0

type PrivateKey struct {
	Data        []byte    `json:"data"`
	Fingerprint string    `json:"fingerprint"`
	CreatedAt   time.Time `json:"created_at"`
}

PrivateKey is a private key the server holds, kept with the fingerprint callers look it up by. Data is the key material itself: never log or serialize a PrivateKey into a response.

type PropertyParams added in v0.3.2

type PropertyParams struct {
	Name     string `json:"name"`
	Operator string `json:"operator"`
	Value    any    `json:"value"`
}

PropertyParams compares one field against one value — the leaf of a filter tree.

type PublicKey added in v0.5.0

type PublicKey struct {
	Data        []byte    `json:"data"`
	Fingerprint string    `json:"fingerprint"`
	CreatedAt   time.Time `json:"created_at"`
	TenantID    string    `json:"tenant_id"`
	PublicKeyFields
}

PublicKey is an SSH key registered in a namespace, and the ACL entry attached to it. Data is the key in wire format; Fingerprint is what the SSH gateway looks it up by during authentication.

type PublicKeyAuthRequest added in v0.5.0

type PublicKeyAuthRequest struct {
	Fingerprint string `json:"fingerprint"`
	Data        string `json:"data"`
}

PublicKeyAuthRequest is what the SSH gateway sends to have a key challenge signed on behalf of a session.

type PublicKeyAuthResponse added in v0.5.0

type PublicKeyAuthResponse struct {
	Signature string `json:"signature"`
}

PublicKeyAuthResponse carries the signature produced for a PublicKeyAuthRequest.

type PublicKeyFields added in v0.5.0

type PublicKeyFields struct {
	Name     string          `json:"name"`
	Username string          `json:"username" validate:"regexp"`
	Filter   PublicKeyFilter `json:"filter" validate:"required"`
}

PublicKeyFields is the editable part of a public key: who it logs in as and which devices it reaches. The key material itself is not here, so an update can change the rule without touching the key.

func (*PublicKeyFields) Validate added in v0.6.1

func (p *PublicKeyFields) Validate() error

Validate checks the fields, including that Username and the filter's Hostname compile as regular expressions — they are patterns, not literals, and an uncompilable one would silently match nothing. It does not require them to be anchored: MatchPattern anchors them at match time.

type PublicKeyFilter added in v0.9.1

type PublicKeyFilter struct {
	Hostname string `json:"hostname,omitempty" validate:"required_without=Tags,excluded_with=Tags,regexp"`
	Taggable `json:",inline"`
}

PublicKeyFilter contains the filter rule of a Public Key.

A PublicKeyFilter can contain either Hostname, string, or Tags, slice of strings never both. Hostname is a regexp matched against the whole device name; see MatchPattern.

func (PublicKeyFilter) Matches

func (f PublicKeyFilter) Matches(device *Device) (bool, error)

Matches reports whether the given device satisfies the filter. A filter is either a hostname pattern matched against the whole device name (see MatchPattern), or a tag set matched by intersection against the device's tag ids; an empty filter matches every device. It is the shared device-selector matcher used by both the public-key ACL and Access Policies.

The device must already carry its tag ids (Taggable.TagIDs) for the tag branch; callers resolving a device from an agent-sent payload must populate them first, since the agent does not send tag ids.

type PublicKeyUpdate added in v0.5.0

type PublicKeyUpdate struct {
	PublicKeyFields
}

PublicKeyUpdate is what an edit may change: the rule, never the key material. Replacing a key means deleting and re-adding it, so its fingerprint stays the identity.

type RecordedSession added in v0.4.0

type RecordedSession struct {
	UID      UID       `json:"uid"`
	Message  string    `json:"message"`
	TenantID string    `json:"tenant_id"`
	Time     time.Time `json:"time"`
	Width    int       `json:"width"`
	Height   int       `json:"height"`
}

RecordedSession is one frame of a recorded terminal session. Recording is a cloud feature and the type lives there too; this copy exists because migrations reference it, so it cannot move until they no longer do.

type SSHApproval

type SSHApproval struct {
	Code        string          `json:"code"`
	TenantID    string          `json:"tenant_id"`
	Kind        SSHApprovalKind `json:"kind"`
	SessionUID  string          `json:"session_uid"`
	SSHID       string          `json:"sshid"`
	DeviceUID   string          `json:"device_uid"`
	DeviceName  string          `json:"device_name"`
	Username    string          `json:"username"`
	IPAddress   string          `json:"ip_address"`
	Fingerprint string          `json:"fingerprint"`
	Data        []byte          `json:"data"`
	// ReauthPeriod is the policy's window in seconds, on a reauth approval. Nil or
	// zero means the policy asks every time.
	ReauthPeriod *int             `json:"reauth_period"`
	State        SSHApprovalState `json:"state"`
	// DecidedBy is the account that resolved the approval. On an identity
	// approval it is the account the key binds to, and the gateway adopts it as
	// the session's identity.
	DecidedBy   string    `json:"decided_by"`
	RequestedAt time.Time `json:"requested_at"`
	ExpiresAt   time.Time `json:"expires_at"`
}

SSHApproval is a decision the SSH gateway parked while it holds a pure-OpenSSH login open, for a member to resolve in the console. The code is its identity and its secret: the gateway prints it in the terminal banner.

type SSHApprovalCreated

type SSHApprovalCreated struct {
	Code      string `json:"code"`
	ExpiresIn int    `json:"expires_in_seconds"`
}

SSHApprovalCreated is the response to creating an approval: the short code the gateway prints, and the window the user has to decide.

type SSHApprovalKind

type SSHApprovalKind string

SSHApprovalKind is what confirming an approval actually does. A native SSH login can wait on either, and both act on the identity, not on the session: the session is only registered once the auth pipeline clears.

const (
	// SSHApprovalIdentity binds the presented key as a new identity.
	SSHApprovalIdentity SSHApprovalKind = "identity"
	// SSHApprovalReauth refreshes the re-auth window of an identity that already
	// exists, because a policy demands a fresh one. It creates nothing.
	SSHApprovalReauth SSHApprovalKind = "reauth"
)

type SSHApprovalRequest

type SSHApprovalRequest struct {
	SSHID       string           `json:"sshid"`
	DeviceName  string           `json:"device_name"`
	Username    string           `json:"username"`
	IPAddress   string           `json:"ip_address"`
	RequestedAt time.Time        `json:"requested_at"`
	State       SSHApprovalState `json:"state"`
	// Code echoes the correlation code so the page can display it for the user to
	// visually match against their terminal banner (anti-phishing).
	Code string `json:"code"`
	// Fingerprint is the presented key's fingerprint, shown front-and-center when
	// the key is becoming an identity.
	Fingerprint string `json:"fingerprint"`
	// Kind is what confirming does, and it is what the console branches the whole
	// screen on.
	Kind SSHApprovalKind `json:"kind"`
	// ReauthPeriod lets the console say how long confirming lasts, which is not
	// this login: the window is per identity, so other logins with the same key
	// skip the browser step until it lapses.
	ReauthPeriod *int `json:"reauth_period,omitempty"`
	// ExpiresIn is how much of the approval window is left, in seconds.
	ExpiresIn int `json:"expires_in_seconds"`
	// Namespace names where the key lands. The login carries it in the SSHID, so
	// it is not the console's current namespace: a member can approve a key into
	// a namespace they are not currently browsing.
	Namespace string `json:"namespace"`
}

SSHApprovalRequest is the detail the console renders so the user sees which key and login they are deciding on.

type SSHApprovalState

type SSHApprovalState string

SSHApprovalState is the lifecycle of an approval. There is no stored "expired" state: a row past ExpiresAt reads as unknown, and a cron prunes it later.

const (
	// SSHApprovalPending is an approval nobody has answered yet. A pending row past its ExpiresAt is
	// no longer pending — it is unknown.
	SSHApprovalPending SSHApprovalState = "pending"
	// SSHApprovalConfirmed is an approval a member granted; the parked login proceeds.
	SSHApprovalConfirmed SSHApprovalState = "confirmed"
	// SSHApprovalRejected is an approval a member denied; the parked login is refused.
	SSHApprovalRejected SSHApprovalState = "rejected"
)

type SSHApprovalStatus

type SSHApprovalStatus struct {
	State  SSHApprovalState `json:"state"`
	UserID string           `json:"user_id,omitempty"`
}

SSHApprovalStatus is what the SSH gateway polls while it holds the login open. UserID carries the approving account once the decision is made, so the gateway can bind it to the session.

type SSHCommand added in v0.19.0

type SSHCommand struct {
	Command string `json:"command"`
}

SSHCommand is the payload of an "exec" request: the single command line the client asked to run instead of an interactive shell.

type SSHExitStatus added in v0.19.0

type SSHExitStatus struct {
	Status uint32 `json:"status"`
}

SSHExitStatus is the payload of an "exit-status" request: the process's exit code, sent when it ended normally rather than on a signal.

type SSHIdentity

type SSHIdentity struct {
	ID       string `json:"id"`
	TenantID string `json:"-"`
	// PrincipalID is the id of the bound principal (a row in the users table,
	// human or service account).
	PrincipalID string `json:"principal_id"`
	// PrincipalName, PrincipalEmail, and PrincipalType describe the bound
	// principal, resolved for the management screen. They are not stored on the
	// identity row. PrincipalType tells a human's key apart from a service
	// account's.
	PrincipalName  string   `json:"principal_name"`
	PrincipalEmail string   `json:"principal_email"`
	PrincipalType  UserType `json:"principal_type"`
	// Fingerprint is the SSH public key fingerprint in "SHA256:…" form.
	Fingerprint string `json:"fingerprint"`
	// Data is the OpenSSH public key the fingerprint is derived from.
	Data []byte `json:"-"`
	// Name is a user label for the key, e.g. "laptop".
	Name string `json:"name"`
	// Source is how the identity came to exist. It is a label, not a boundary:
	// only the approval path is asserted by the server, so nothing may authorize
	// on it without moving that decision server-side first.
	Source    SSHIdentitySource `json:"source"`
	CreatedAt time.Time         `json:"created_at"`
	// LastUsedAt moves on every connect (identity resolution).
	LastUsedAt *time.Time `json:"last_used_at"`
	// LastReauthAt moves only on a successful re-authentication, so it can gate
	// an Access Policy's reauth_period freshness window. Distinct from LastUsedAt.
	LastReauthAt *time.Time `json:"last_reauth_at"`
	// ExpiresAt, SingleUse, and ConsumedAt are the key's lifecycle: it is dead
	// once expired or consumed. Any identity may carry a TTL; SingleUse is only
	// offered to a service account, whose one key serves one automated run.
	// ExpiresAt nil means it never expires.
	ExpiresAt *time.Time `json:"expires_at"`
	SingleUse bool       `json:"single_use"`
	// ConsumedAt is stamped when a single-use key is burned by its one session.
	ConsumedAt *time.Time `json:"consumed_at"`
}

SSHIdentity binds an SSH public key to a principal (a human user or a service account) within a namespace. In the identity SSH access mode the key is the credential: a connection whose presented key's fingerprint resolves to an identity is recognized as that principal, without a browser step. A fingerprint maps to exactly one identity per namespace (UNIQUE(namespace_id, fingerprint)); the same key may be enrolled in other namespaces, and a principal may hold many keys per namespace.

func (*SSHIdentity) Active

func (i *SSHIdentity) Active(now time.Time) bool

Active reports whether the key is still usable at now: neither consumed nor past its expiry. A nil ExpiresAt never expires, so a key created without a TTL is always active.

type SSHIdentitySource

type SSHIdentitySource string

SSHIdentitySource is how an identity came to exist.

const (
	// SSHIdentitySourceManual is a public key pasted on the SSH Identities page.
	SSHIdentitySourceManual SSHIdentitySource = "manual"
	// SSHIdentitySourceBrowser is the web terminal's own key. It is generated in
	// the browser and held non-extractably, so it can only ever be presented from
	// that browser and it dies with the browser's site data.
	SSHIdentitySourceBrowser SSHIdentitySource = "browser"
	// SSHIdentitySourceApproval is a key accepted at login, after a native client
	// offered one the namespace did not know yet.
	SSHIdentitySourceApproval SSHIdentitySource = "approval"
)

type SSHPty added in v0.19.0

type SSHPty struct {
	Term    string `json:"term"`
	Columns uint32 `json:"columns"`
	Rows    uint32 `json:"rows"`
	Width   uint32 `json:"width"`
	Height  uint32 `json:"height"`
	// Not persisted (json:"-"); kept only so gossh.Unmarshal can consume it.
	Modelist []byte `json:"-"`
}

SSHPty is the payload of a "pty-req" request, the terminal the client asks for before a shell. It repeats SSHWindowChange's fields rather than embedding it because ssh.Unmarshal maps a flat wire format onto the struct and cannot descend into a nested one.

type SSHPtyOutput added in v0.19.0

type SSHPtyOutput struct {
	Output string `json:"output"`
}

SSHPtyOutput carries the bytes a terminal produced. It has no SSH request of its own — the protocol sends this as channel data — and exists so recorded output can be stored as an event alongside the requests.

type SSHSignal added in v0.19.0

type SSHSignal struct {
	Name    uint32 `json:"status"`
	Dumped  bool   `json:"dumped"`
	Message string `json:"message"`
	Lang    string `json:"lang"`
}

SSHSignal is the payload of an "exit-signal" request, sent instead of an exit status when the process was killed. Dumped says whether a core file was written.

type SSHSubsystem added in v0.19.0

type SSHSubsystem struct {
	Subsystem string `json:"subsystem"`
}

SSHSubsystem is the payload of a "subsystem" request, which is how SFTP and friends are asked for by name rather than by command line.

type SSHWindowChange added in v0.19.0

type SSHWindowChange struct {
	Columns uint32 `json:"columns"`
	Rows    uint32 `json:"rows"`
	Width   uint32 `json:"width"`
	Height  uint32 `json:"height"`
}

SSHWindowChange is the payload of a "window-change" request, sent every time the client's terminal is resized. Columns and rows are what the application sees; width and height are pixels and are usually zero.

type ServiceAccount

type ServiceAccount struct {
	// ID is the underlying service user's id.
	ID        string    `json:"id"`
	Name      string    `json:"name"`
	CreatedAt time.Time `json:"created_at"`
	// Identities are the SSH keys enrolled for this service account in the namespace.
	Identities []SSHIdentity `json:"identities"`
}

ServiceAccount is a non-human principal for automated systems (CI, backups, config management): a service-typed user (see UserTypeService) plus a namespace membership that holds one or more SSH identities. It never signs in to the console and is not an API principal, existing only for the SSH identity scheme. It is authorized by the same Access Policies as human members; the human/service distinction lives in the user's type, not in the policy.

type Session

type Session struct {
	UID       string  `json:"uid"`
	DeviceUID UID     `json:"device_uid,omitempty"`
	Device    *Device `json:"device"`
	TenantID  string  `json:"tenant_id"`
	Username  string  `json:"username"`
	// UserID is the ShellHub account that authorized this session via browser
	// approval. Empty for password/public-key logins and web-terminal sessions.
	UserID        string          `json:"user_id,omitempty"`
	IPAddress     string          `json:"ip_address"`
	StartedAt     time.Time       `json:"started_at"`
	LastSeen      time.Time       `json:"last_seen"`
	Active        bool            `json:"active"`
	Closed        bool            `json:"-"`
	Authenticated bool            `json:"authenticated"`
	Recorded      bool            `json:"recorded"`
	Type          string          `json:"type"`
	Term          string          `json:"term"`
	Web           bool            `json:"web"`
	Position      SessionPosition `json:"position"`
	Events        SessionEvents   `json:"events"`
}

Session is one SSH connection to one device, live or finished. It is created when the connection is established and outlives it: Active says whether it is still running, and Closed distinguishes a session that ended cleanly from one whose device vanished.

type SessionEvent added in v0.18.0

type SessionEvent struct {
	// Session is the session UID where the event occurred.
	Session string `json:"session"`
	// Type of the session. Normally, it is the SSH request name.
	Type SessionEventType `json:"type"`
	// Timestamp contains the time when the event was logged.
	Timestamp time.Time `json:"timestamp"`
	// Data is a generic structure containing data of the event, normally the unmarshaling data of the request.
	Data any `json:"data"`
	// Seat is the seat where the event occurred.
	Seat int `json:"seat"`
}

SessionEvent represents a session event.

type SessionEventType added in v0.19.0

type SessionEventType string

SessionEventType names the SSH request an event came from. The values are the wire names from the SSH protocol, not names of our own, so they can be matched against a packet capture.

const (
	SessionEventTypePtyOutput SessionEventType = "pty-output"

	SessionEventTypePtyRequest   SessionEventType = "pty-req"
	SessionEventTypeWindowChange SessionEventType = "window-change"
	SessionEventTypeExitCode     SessionEventType = "exit-code"

	SessionEventTypeExitStatus SessionEventType = "exit-status"
	SessionEventTypeExitSignal SessionEventType = "exit-signal"

	SessionEventTypeEnv       SessionEventType = "env"
	SessionEventTypeShell     SessionEventType = "shell"
	SessionEventTypeExec      SessionEventType = "exec"
	SessionEventTypeSubsystem SessionEventType = "subsystem"

	SessionEventTypeSignal       SessionEventType = "signal"
	SessionEventTypeTcpipForward SessionEventType = "tcpip-forward"
	SessionEventTypeAuthAgentReq SessionEventType = "auth-agent-req"
)

The event types a session can record. All but pty-output are SSH request names as they appear on the wire; pty-output is ours, carrying the bytes the terminal produced, which SSH itself sends as channel data rather than as a request.

type SessionEvents added in v0.18.0

type SessionEvents struct {
	// Types field is a set of sessions type to simplify the indexing on the database.
	Types []string `json:"types"`
	// Seats contains a list of seats of events.
	Seats []int `json:"seats"`
}

SessionEvents stores the events registered in a session.

type SessionPosition added in v0.10.0

type SessionPosition struct {
	Longitude float64 `json:"longitude"`
	Latitude  float64 `json:"latitude"`
}

SessionPosition is where the client connected from, resolved from its address by GeoIP. It is recorded once at session start and not refreshed.

type SessionSeat added in v0.19.0

type SessionSeat struct {
	// ID is the identifier of session's seat.
	ID int `json:"id"`
}

SessionSeat stores a session's seat.

type SessionUpdate added in v0.16.0

type SessionUpdate struct {
	Recorded      *bool   `json:"recorded"`
	Authenticated *bool   `json:"authenticated"`
	Type          *string `json:"type"`
}

SessionUpdate is a partial update to a session: a nil field is left alone, which is why every field is a pointer.

type Stats

type Stats struct {
	RegisteredDevices int `json:"registered_devices"`
	OnlineDevices     int `json:"online_devices"`
	ActiveSessions    int `json:"active_sessions"`
	PendingDevices    int `json:"pending_devices"`
	RejectedDevices   int `json:"rejected_devices"`
}

Stats is the dashboard's counter set for one namespace. The device counts partition by status, so registered, pending and rejected do not overlap and do not sum to the namespace's limit.

type Status added in v0.7.3

type Status struct {
	Authenticated bool `json:"authenticated"`
}

Status is the authentication state of a session, as the agent reports it back once the SSH handshake has completed.

type System added in v0.17.1

type System struct {
	Setup bool `json:"setup"`
	// InstanceTenantID binds the instance to its namespace in single-namespace (Community)
	// deployments. When set, the store refuses any further namespace creation. Enterprise/Cloud
	// leave it empty (the store wrapper strips it) to keep multi-tenant behavior.
	InstanceTenantID string `json:"instance_tenant_id"`
	// Authentication manages the settings for available authentication methods.
	Authentication *SystemAuthentication `json:"authentication"`
}

System is the instance-wide configuration, of which exactly one row exists. It is read on paths that must work before any namespace exists, such as setup and login.

type SystemAuthentication added in v0.18.0

type SystemAuthentication struct {
	Local *SystemAuthenticationLocal `json:"local"`
}

SystemAuthentication groups the authentication methods an instance offers. A nil method is disabled, which is why each is a pointer.

type SystemAuthenticationLocal added in v0.18.0

type SystemAuthenticationLocal struct {
	// Enabled indicates whether manual authentication using a username and password is enabled or
	// not.
	Enabled bool `json:"enabled" bool:"enabled"`
}

SystemAuthenticationLocal configures username-and-password authentication.

type Tag added in v0.21.0

type Tag struct {
	ID        string    `json:"-"`
	TenantID  string    `json:"tenant_id"`
	Name      string    `json:"name"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

Tag is a named label owned by a namespace. Devices reference it by ID, so renaming a tag keeps every device that carries it.

type TagConflicts added in v0.21.0

type TagConflicts struct {
	Name string
}

TagConflicts names the fields that must be unique within a namespace, and is how a store reports which one collided.

type Taggable added in v0.21.0

type Taggable struct {
	// TagIDs contains the IDs of associated tags. It is used only for database storage
	// and relationship management. The field is not exposed in JSON responses to keep
	// the API focused on meaningful tag data rather than internal identifiers.
	TagIDs []string `json:"-"`

	// Tags contains the complete Tag objects associated with this resource. This field
	// is populated from TagIDs when retrieving data from the database, but is not
	// stored directly. It is used only for JSON serialization to provide clients
	// with full tag information.
	Tags []Tag `json:"tags"`
}

Taggable is an embeddable struct that adds tagging capability to other models.

Example usage:

type Device struct {
    Taggable    // Embed the Taggable struct
    Name string // Other device fields
}

type Tenant added in v0.3.3

type Tenant struct {
	ID string
}

Tenant carries the namespace a request acts on, as the API's gateway reads it out of the request context. It is a struct rather than a string so an absent tenant is a nil pointer, which is the difference between "no namespace" and "the empty namespace".

type Type added in v0.18.0

type Type string

Type is a namespace's kind. It decides whether the namespace can hold more than one member, and cloud billing reads it to pick a plan.

const (
	// TypePersonal is a single-owner namespace with no invitations.
	TypePersonal Type = "personal"
	// TypeTeam is a namespace that can hold members, and the type a namespace gets by default.
	TypeTeam Type = "team"
)

func NewDefaultType added in v0.18.0

func NewDefaultType() Type

NewDefaultType returns the type a namespace is created with when the caller does not choose one.

type UID

type UID string

UID identifies a device or a session — the opaque, externally visible identifier a client addresses a resource by, not the store's primary key.

type User

type User struct {
	ID string `json:"id,omitempty"`
	// Type distinguishes a human user from a service account. It defaults to
	// [UserTypeHuman]; service accounts are created only through the service-account flow.
	Type UserType `json:"type"`
	// Origin specifies the the user's signup method.
	Origin UserOrigin `json:"-"`

	// ExternalID represents the user's identifier in an external system. It is always empty when [User.Origin]
	// is [UserOriginLocal].
	ExternalID string `json:"-"`

	Status UserStatus `json:"status"`
	// MaxNamespaces represents the count of namespaces that the user can owns.
	MaxNamespaces  int       `json:"max_namespaces"`
	CreatedAt      time.Time `json:"created_at"`
	LastLogin      time.Time `json:"last_login"`
	EmailMarketing bool      `json:"email_marketing"`
	UserData
	// MFA contains attributes related to a user's MFA settings. Use [UserMFA.Enabled] to
	// check if MFA is active for the user.
	//
	// NOTE: MFA is available as a cloud-only feature and must be ignored in community.
	MFA         UserMFA         `json:"mfa"`
	Preferences UserPreferences `json:"preferences"`
	Password    UserPassword
	// Admin indicates whether the user has administrative privileges.
	Admin bool `json:"admin"`
	// AwaitingApproval marks a provisioned account that a namespace admin created but a system
	// admin has not approved yet. While true the account is inert: only an admin can mint its
	// activation link. It is set false when an admin creates the account directly or approves it.
	AwaitingApproval bool `json:"awaiting_approval"`
}

User is an account on the instance. It exists independently of any namespace: membership is what ties a user to one, so a user with no memberships is valid and simply sees nothing.

type UserAuthIdentifier added in v0.14.0

type UserAuthIdentifier string

UserAuthIdentifier is an username or email used to authenticate.

func (*UserAuthIdentifier) IsEmail added in v0.14.0

func (i *UserAuthIdentifier) IsEmail() bool

IsEmail checks if the identifier is an email.

type UserAuthMethod added in v0.18.0

type UserAuthMethod string

UserAuthMethod is a way a user may authenticate. A user can hold several at once, so this is a set rather than a mode.

const (
	// UserAuthMethodLocal indicates that the user can authenticate using an email and password.
	UserAuthMethodLocal UserAuthMethod = "local"

	// UserAuthMethodSAML indicates that the user can authenticate using a third-party SAML application.
	UserAuthMethodSAML UserAuthMethod = "saml"
)

func (UserAuthMethod) String added in v0.18.0

func (a UserAuthMethod) String() string

type UserAuthResponse

type UserAuthResponse struct {
	Token         string           `json:"token"`
	User          string           `json:"user"`
	Origin        string           `json:"origin"`
	AuthMethods   []UserAuthMethod `json:"auth_methods"`
	Name          string           `json:"name"`
	ID            string           `json:"id"`
	Tenant        string           `json:"tenant"`
	Email         string           `json:"email"`
	RecoveryEmail string           `json:"recovery_email"`
	Role          string           `json:"role"`
	MFA           bool             `json:"mfa"`
	MaxNamespaces int              `json:"max_namespaces"`
	Admin         bool             `json:"admin"`
}

UserAuthResponse is what a successful login returns: the token the client authenticates with from then on, plus enough of the user and their current namespace for the console to render without a second round trip.

type UserData added in v0.8.0

type UserData struct {
	Name     string `json:"name" validate:"required,name"`
	Username string `json:"username" validate:"required,username"`
	Email    string `json:"email" validate:"required,email"`
	// RecoveryEmail is a custom, non-unique email address that a user can use to recover their account
	// when they lose access to all other methods. It must never be equal to [UserData.Email].
	//
	// NOTE: Recovery email is available as a cloud-only feature and must be ignored in community.
	RecoveryEmail string `json:"recovery_email" validate:"omitempty,email"`
}

UserData is the identifying half of a user, split out because these are the fields the API lets a user change and the fields uniqueness is enforced on.

type UserInfo added in v0.17.0

type UserInfo struct {
	// OwnedNamespaces are the namespaces where the user is the owner.
	OwnedNamespaces []Namespace
	// AssociatedNamespaces are the namespaces where the user is a member.
	AssociatedNamespaces []Namespace
}

UserInfo is the namespaces a user can reach, split by whether they own them. A namespace appears in exactly one of the two lists.

type UserInvitation

type UserInvitation struct {
	ID          string               `json:"id"`
	Email       string               `json:"email"`
	CreatedAt   time.Time            `json:"created_at"`
	UpdatedAt   time.Time            `json:"updated_at"`
	Invitations int                  `json:"invitations"`
	Status      UserInvitationStatus `json:"status"`
}

UserInvitation is an offer to create an account on the instance, addressed to an email that has none. It is distinct from MembershipInvitation, which offers an existing account a place in a namespace.

type UserInvitationStatus

type UserInvitationStatus string

UserInvitationStatus is where an invitation to create an account sits. Unlike a membership invitation it cannot be rejected: it is either open or used.

const (
	// UserInvitationStatusPending is an invitation whose account has not been created yet.
	UserInvitationStatusPending UserInvitationStatus = "pending"
	// UserInvitationStatusAccepted is an invitation that has been used to create an account, and
	// cannot be used again.
	UserInvitationStatusAccepted UserInvitationStatus = "accepted"
)

type UserMFA added in v0.16.0

type UserMFA struct {
	// Enabled reports whether MFA is enabled for the user.
	Enabled bool `json:"enabled"`
	// Secret is the key used for authenticating with the OTP server.
	Secret string `json:"-"`
	// RecoveryCodes are recovery tokens that the user can use to regain account access if they lose their MFA device.
	RecoveryCodes []string `json:"-"`
}

UserMFA represents the attributes related to MFA for a user.

type UserOrigin added in v0.18.0

type UserOrigin string

UserOrigin records how the account came into existence, which decides who owns the password: a SAML user has none here, and authenticates against the IdP instead.

const (
	// UserOriginLocal indicates that the user was created through the standard signup process, without
	// using third-party integrations like SSO IdPs.
	UserOriginLocal UserOrigin = "local"

	// UserOriginSAML indicates that the user was created using a SAML method.
	UserOriginSAML UserOrigin = "SAML"
)

func (UserOrigin) String added in v0.18.0

func (o UserOrigin) String() string

type UserPassword added in v0.8.0

type UserPassword struct {
	// Plain contains the plain text password.
	Plain string `json:"password" validate:"required,password"`
	// Hash contains the hashed pasword from plain text.
	Hash string `json:"-"`
}

UserPassword holds a password in both forms. Plain is populated on the way in from a request and never persisted or serialized; Hash is what the store keeps. Build one with HashUserPassword rather than setting Hash yourself.

func HashUserPassword added in v0.15.0

func HashUserPassword(plain string) (UserPassword, error)

HashUserPassword receives a plain password and hash it, returning a UserPassword.

func (*UserPassword) Compare added in v0.14.0

func (p *UserPassword) Compare(plain string) bool

Compare reports whether a plain password matches with hash.

For compatibility purposes, it can compare using both SHA256 and bcrypt algorithms. Hashes starting with "$" are assumed to be a bcrypt hash; otherwise, they are treated as SHA256 hashes.

type UserPreferences added in v0.16.0

type UserPreferences struct {
	// PreferredNamespace represents the namespace the user most recently authenticated with.
	PreferredNamespace string `json:"-"`

	// AuthMethods indicates the authentication methods that the user can use to authenticate.
	AuthMethods []UserAuthMethod `json:"auth_methods"`
}

UserPreferences is per-user state that changes how the console behaves for them, and carries no authorization weight: nothing here decides what the user may do.

type UserStatus added in v0.17.0

type UserStatus string

UserStatus is where a user sits in the sign-up flow. It gates authentication, so a user who is not confirmed cannot sign in even with the right password.

const (
	// UserStatusNotConfirmed applies to cloud-only instances. This status is assigned to a user who has registered
	// but has not yet confirmed their email address.
	UserStatusNotConfirmed UserStatus = "not-confirmed"

	// UserStatusConfirmed indicates that the user has completed the registration process and confirmed their email address.
	// Users in community and enterprise instances will always be created with this status.
	UserStatusConfirmed UserStatus = "confirmed"
)

func (UserStatus) String added in v0.17.0

func (s UserStatus) String() string

type UserTokenRecover added in v0.7.2

type UserTokenRecover struct {
	Token     string    `json:"uid"`
	User      string    `json:"user_id"`
	CreatedAt time.Time `json:"created_at"`
}

UserTokenRecover is a password-recovery token. Recovery is a cloud feature and the type lives there too; this copy exists because migrations reference it, so it cannot move until they no longer do.

type UserType

type UserType string

UserType separates a person from a service account. It is not the membership role — the role says what a principal may do in a namespace, the type says what kind of principal it is.

const (
	// UserTypeHuman is a regular person: signs in to the console, may hold API keys, and is
	// authorized by their membership role.
	UserTypeHuman UserType = "human"

	// UserTypeService is a service account: a non-human principal that only holds an SSH
	// identity for automated systems. It never signs in to the console and is not an API
	// principal. This type is the human/service discriminator, not the membership role, so it
	// stays valid if roles ever become groups.
	UserTypeService UserType = "service"
)

func (UserType) String

func (t UserType) String() string

type Username added in v0.5.0

type Username struct {
	ID string
}

Username carries the authenticated user's username as the API's gateway reads it out of a request's context, alongside ID and Tenant.

type WebEndpoint

type WebEndpoint struct {
	Address   string         `json:"address"`
	Namespace string         `json:"namespace"`
	DeviceUID string         `json:"device_uid"`
	Host      string         `json:"host"`
	Port      int            `json:"port"`
	TLS       WebEndpointTLS `json:"tls"`
}

WebEndpoint is what the HTTP proxy needs to route a request to a device: which namespace and device to dial, and which backend address to ask that device for. It is deliberately narrower than the stored endpoint — the proxy has no use for its expiration or creation time.

type WebEndpointTLS

type WebEndpointTLS struct {
	Enabled bool `json:"enabled"`
	Verify  bool `json:"verify"`
	// Domain doubles as the Host header override and, with TLS enabled, the SNI
	// sent during the handshake.
	Domain string `json:"domain"`
}

WebEndpointTLS carries how the HTTP proxy should reach the backend behind a web endpoint.

Jump to

Keyboard shortcuts

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