Documentation
¶
Overview ¶
Package main — the Hive relay: a content-blind rendezvous + envelope-forwarding server. Clients post opaque (end-to-end encrypted) event envelopes keyed by workspace and fetch everything after a cursor; the relay also brokers short pairing codes, an identity directory, a poll-based account inbox, the friend graph, and presence. It never sees plaintext.
This is a Go port of the reference Rust relay (crates/hive-relay), preserving the JSON /v1 wire contract and the hrt1 entitlement-token format byte-for-byte so existing clients and issued tokens keep working.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrSelfRequest = errors.New("cannot friend yourself") ErrAlreadyFriends = errors.New("already friends") ErrCapReached = errors.New("friend cap reached") ErrNotFound = errors.New("request not found") ErrNotYours = errors.New("not your request") ErrNotPending = errors.New("request not pending") ErrTooManyPending = errors.New("too many pending outbound requests") )
Functions ¶
Types ¶
type DeviceRow ¶
type DeviceRow struct {
DeviceID string `json:"deviceId"`
NodeID *string `json:"nodeId"`
Label *string `json:"label"`
LastSeen int64 `json:"lastSeen"`
}
DeviceRow is a registered device as returned to callers.
type DirAccount ¶
type DirAccount struct {
GitHubID uint64
Login string
Name *string
Devices map[string]string // device id → ka public key
}
DirAccount is a directory entry: a verified GitHub identity + each device's X25519 key-agreement public key (opaque to the relay), so a teammate can be invited by @handle and the workspace key sealed to all their devices.
type EntitlementVerifier ¶
type EntitlementVerifier interface {
Allow(token string, nowUnix int64) (*TokenClaims, bool)
}
EntitlementVerifier decides whether a presented bearer token is admitted at nowUnix. The returned claims (non-nil for signed tokens) let downstream enforcement read per-plan limits + RBAC capabilities.
func EntitlementFromEnv ¶
func EntitlementFromEnv() EntitlementVerifier
EntitlementFromEnv is the exported, interface-typed constructor the OSS binary and downstream builds use to get the default env-driven verifier.
type Envelope ¶
type Envelope struct {
Seq uint64 `json:"seq"`
Body json.RawMessage `json:"body"`
}
Envelope is one stored workspace event: a monotonic server sequence + the opaque body verbatim.
type FriendPresence ¶
FriendPresence is a friend plus their presence state.
type FriendRequest ¶
type FriendRequest struct {
ID string
FromAccount string
FromLogin string
ToAccount string
ToLogin string
CreatedAt int64
State RequestState
}
FriendRequest is a pending/closed friend request. FromAccount/ToAccount are account keys (github:<id>); the relay stamps From from the verified GitHub token, so a request can't be forged to look like another user.
type Hooks ¶
type Hooks interface {
// WorkspaceWritten fires after a durable workspace write (envelope or key
// rotation), carrying the assigned server sequence — the natural metering
// + audit point.
WorkspaceWritten(ctx context.Context, workspace string, seq uint64, claims *TokenClaims)
}
Hooks observe successful operations for audit + usage metering. All methods must tolerate a nil claims (open / token-allowlist policies carry none). The open relay leaves this nil (no-op).
type InboxRow ¶
type InboxRow struct {
Seq uint64 `json:"seq"`
Body json.RawMessage `json:"body"`
}
InboxRow is one account-channel event (same shape as Envelope on the wire).
type Options ¶
type Options struct {
Store Store // required
Entitlement EntitlementVerifier // nil → EntitlementFromEnv()
WriteGuard WriteGuard // nil → allow all writes (content-blind)
Hooks Hooks // nil → no-op
FriendCap *int // nil → unlimited
}
Options configure a Server. Only Store is required; the rest default to the open-relay behavior. Downstream builds may inject implementations of the seams (see seams.go) here without touching this package.
type RequestState ¶
type RequestState string
RequestState is the lifecycle of a friend request.
const ( StatePending RequestState = "pending" StateAccepted RequestState = "accepted" StateRejected RequestState = "rejected" StateCancelled RequestState = "cancelled" )
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server holds the durable Store plus the ephemeral, instance-local pieces (short pairing codes), the entitlement verifier, and the optional seams.
type Store ¶
type Store interface {
// Workspace sync (opaque envelopes + rendezvous + key rotations).
AppendEnvelope(ctx context.Context, workspace string, body json.RawMessage) (uint64, error)
EnvelopesAfter(ctx context.Context, workspace string, after uint64) ([]Envelope, error)
PutCandidate(ctx context.Context, workspace, deviceID string, candidate json.RawMessage) error
Candidates(ctx context.Context, workspace string) (map[string]json.RawMessage, error)
PutPresence(ctx context.Context, workspace, deviceID string, presence json.RawMessage) error
PresenceBlobs(ctx context.Context, workspace string) (map[string]json.RawMessage, error)
AppendKeyRotation(ctx context.Context, workspace string, blob json.RawMessage) error
KeyRotations(ctx context.Context, workspace string) ([]json.RawMessage, error)
// Identity directory (invite-by-handle, seal-to-all-devices).
UpsertDirDevice(ctx context.Context, githubID uint64, login string, name *string, deviceID, kaPub string) error
DirAccountByHandle(ctx context.Context, handle string) (*DirAccount, error)
// Account registry + per-account inbox (the social channel).
RegisterAccountDevice(ctx context.Context, githubID uint64, login, deviceID string, nodeID, label *string, now int64) (string, error)
HeartbeatDevice(ctx context.Context, githubID uint64, deviceID string, now int64) (bool, error)
PushAccountEvent(ctx context.Context, accountKey string, body json.RawMessage) (uint64, error)
AccountInboxAfter(ctx context.Context, accountKey string, after uint64) ([]InboxRow, error)
AccountDevices(ctx context.Context, accountKey string) ([]DeviceRow, error)
AccountKeyForLogin(ctx context.Context, login string) (string, bool, error)
SetVisibility(ctx context.Context, githubID uint64, appearOffline bool) error
PresenceOf(ctx context.Context, accountKey string, now int64) (string, error)
// Friend graph.
AreFriends(ctx context.Context, a, b string) (bool, error)
CreateFriendRequest(ctx context.Context, fromAccount, fromLogin, toAccount, toLogin string, now int64, friendCap *int) (FriendRequest, error)
AcceptFriendRequest(ctx context.Context, requestID, acceptor string, friendCap *int) (FriendRequest, error)
CloseFriendRequest(ctx context.Context, requestID, actor string) (FriendRequest, error)
RemoveFriend(ctx context.Context, account, other string) (bool, error)
ListFriends(ctx context.Context, account string) ([]Friend, error)
IncomingRequests(ctx context.Context, account string, now int64) ([]FriendRequest, error)
// FriendDevices returns a friend's devices; the bool is false (→ 403) when
// caller and friend are not accepted friends.
FriendDevices(ctx context.Context, caller, friend string) ([]DeviceRow, bool, error)
FriendCount(ctx context.Context, account string) (int, error)
FriendPresence(ctx context.Context, account string, now int64) ([]FriendPresence, error)
// Durability (snapshot-backed stores only; no-op otherwise).
Flush(ctx context.Context) error
PersistenceEnabled() bool
Close() error
}
── Store: the durable-state seam ────────────────────────────────────────────
Every piece of state that must survive restarts and (for HA) be shared across instances goes through this interface, so the backend is a deployment choice with no future data migration:
- memoryStore — in-process maps + an optional JSON snapshot (self-host default, zero deps, what tests run against).
- postgresStore (Phase 2) — a shared SQL store selected via DATABASE_URL, so running multiple relay instances works with no data migration.
Ephemeral, instance-local state (short pairing codes) stays out of here — see the Server. friendCap is threaded through the friend-graph methods so the store stays free of policy.
func NewMemoryStore ¶
func NewMemoryStore() Store
NewMemoryStore returns an in-process, ephemeral Store (no persistence).
func NewMemoryStoreWithPersistence ¶
NewMemoryStoreWithPersistence returns an in-process Store backed by a JSON snapshot under dataDir (loaded on boot, flushed periodically).
type TokenClaims ¶
type TokenClaims struct {
Sub string `json:"sub"`
Plan string `json:"plan"`
Exp uint64 `json:"exp"`
MaxMembers *uint32 `json:"max_members"`
RetentionDays *uint32 `json:"retention_days"`
Turn bool `json:"turn"`
Caps []string `json:"caps"`
}
TokenClaims are carried by a signed entitlement token. Forward-compatible: unknown fields are ignored on decode, and the relay ignores any capability it does not yet enforce. Field names match the Rust issuer's serde output.
func (TokenClaims) HasCap ¶
func (c TokenClaims) HasCap(cap string) bool
HasCap reports whether the subject holds a named RBAC capability.
func (TokenClaims) IsExpired ¶
func (c TokenClaims) IsExpired(nowUnix int64) bool
IsExpired reports whether Exp is set and now-or-past.
type WriteGuard ¶
type WriteGuard interface {
CheckWrite(ctx context.Context, workspace string, claims *TokenClaims, r *http.Request) error
}
WriteGuard runs before any workspace write (envelopes / keyring / candidates / presence). Return a non-nil error to reject the write (mapped to 403). Default nil = pure content-blind forwarding. A downstream build can set one to enforce workspace membership / roles from the verified claims.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
hive-relay
command
Command hive-relay is the open-source Hive relay binary: a content-blind rendezvous + envelope-forwarding server.
|
Command hive-relay is the open-source Hive relay binary: a content-blind rendezvous + envelope-forwarding server. |