setup

package
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jun 20, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package setup is the setup-wizard backend (Phase 13 / Slice 9a, UX-03): an isolated loopback HTTP server (:9081, distinct from the AG-UI gateway :9080) that issues the single-use onboarding token the Telegram channel's /start consumes (plan 13-06) and surfaces onboarding completion over SSE. The API is backend-only this phase (the HTML frontend is deferred, D-03); the real onboarding path is a terminal ASCII QR of the deep-link (qrterminal), with the qr_svg JSON field kept empty for forward-compat (OQ4).

Every /setup/* route is gated by a one-time in-memory token (requireSetupToken, amendment #10): generated as a random UUIDv4 + printed once to stdout on first boot when AURA_SETUP_TOKEN is empty, held in memory only (no disk), and invalidated after onboarding completes. The bot token supplied to /setup/token is validated via telebot getMe and is NEVER logged (T-13-07-BotTokenLeak).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BotProbe

type BotProbe func(ctx context.Context, token string) (username string, err error)

BotProbe validates a Telegram bot token and returns the bot username. In production it is a telebot getMe (tele.NewBot does a live getMe; the username is bot.Me.Username); in tests it is a mock. It MUST NOT log the token — the caller passes a secret. An invalid token returns a non-nil error.

type Deps

type Deps struct {
	Store      Store
	Probe      BotProbe
	Bind       string
	Token      string
	IdentityID string
	TokenOut   io.Writer // os.Stdout in prod; a buffer in tests (nil → os.Stdout)
	// QROut is where the onboarding ASCII QR is rendered (os.Stdout in prod; a
	// discard/buffer in tests to keep output quiet). nil → os.Stdout.
	QROut io.Writer
	// PollInterval is the SSE consumed_at poll period (default 2s, D-03). A test
	// shortens it so the SSE assertion does not wait 2s for a tick.
	PollInterval time.Duration
}

Deps carries everything NewServer needs. Bind defaults to the loopback :9081 (config resolves AURA_SETUP_BIND); Token is the configured AURA_SETUP_TOKEN (empty → boot-generated, printed to TokenOut); IdentityID is the local identity an onboarding token FKs to (the composition root resolves the `local` seed in 13-09).

type InsertPendingParams

type InsertPendingParams struct {
	OnboardingToken string
	IdentityID      string
	GeneratedBy     string
	ExpiresAt       time.Time
}

InsertPendingParams is the plain projection the setup server passes to the Store to mint one onboarding token. It mirrors telegram.InsertPendingParams field-for-field (declared here so the setup package needs no telegram import; the composition root adapts one onto the other in 13-09).

type OnboardLinkResponse

type OnboardLinkResponse struct {
	DeepLink string `json:"deep_link"`
	QRSVG    string `json:"qr_svg"`
}

OnboardLinkResponse is the POST /setup/onboard-link result: the deep-link a user taps to onboard plus the qr_svg body. qr_svg is DEFERRED this phase (OQ4) — it stays in the contract for the future frontend but is always the empty string; the real onboarding QR is the terminal ASCII one printed to stdout.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is the isolated loopback setup-wizard HTTP gateway (:9081). It is the thinnest glue over the narrow Store + the BotProbe seam + the one-time Token; the bind is loopback by default (T-13-07-SetupExposure) and the token gate is mandatory on every route (T-13-07-SetupToken). botConfigured tracks whether a bot token has been supplied (via /setup/token) for the status footer.

func NewServer

func NewServer(deps Deps) *Server

NewServer builds the setup gateway over the supplied deps. An empty Deps.Token generates a one-time UUIDv4 printed to TokenOut (os.Stdout when nil). A non-positive PollInterval falls back to the 2s default.

func (*Server) HTTPServer

func (s *Server) HTTPServer(bind string) *http.Server

HTTPServer builds the *http.Server the daemon runs (loopback bind + a slow-loris-defanging ReadHeaderTimeout, mirrors serve.go). The composition root (13-09) owns ListenAndServe + the graceful Shutdown call.

func (*Server) InvalidateToken

func (s *Server) InvalidateToken()

InvalidateToken burns the one-time token after onboarding completes (the channel persisted the account). A subsequent /setup/* call 401s. Exposed so the SSE pump (handleEvents) and the composition root can fire it.

func (*Server) Mux

func (s *Server) Mux() http.Handler

Mux registers the four routes using Go 1.22+ method-pattern routing (no chi/gorilla — matches the no-router codebase posture, mirrors agui.Server.Mux) each wrapped by requireSetupToken (T-13-07-SetupToken: the gate is mandatory on every /setup/* route, not a subset).

type SetupEvent

type SetupEvent struct {
	Type           string `json:"type"`
	TelegramUserID int64  `json:"telegram_user_id,omitempty"`
	Username       string `json:"username,omitempty"`
}

SetupEvent is one Server-Sent Event on GET /setup/events. The only event type emitted this phase is "onboarding_completed", carrying the Telegram user id + username of the account that just consumed its onboarding token.

type StatusResponse

type StatusResponse struct {
	BotConfigured bool   `json:"bot_configured"`
	AccountCount  int64  `json:"account_count"`
	LastActivity  string `json:"last_activity"`
}

StatusResponse is the GET /setup/status result: whether a bot token is configured, how many accounts have onboarded, and the last-activity marker.

type Store

type Store interface {
	InsertPending(ctx context.Context, p InsertPendingParams) error
	PendingConsumed(ctx context.Context, onboardingToken string) (bool, error)
	CountAccounts(ctx context.Context) (int64, error)
}

Store is the narrow DB seam the setup handlers consume (D-A2-02, declared consumer-side so the server depends only on the methods it calls and tests can pass a fake). *telegram.Store satisfies it implicitly: InsertPending mints an onboarding row, PendingConsumed polls the SSE completion signal (consumed_at), CountAccounts backs the status footer.

NOTE: the 0012 telegram_setup_pending schema records consumed_at but NOT which telegram_user_id consumed the token — there is no token→account link to follow (a consumed token shares only the identity_id, which is the single `local` identity here). So the onboarding_completed SSE event is keyed purely on the consumed_at signal; its telegram_user_id/username fields stay omitempty rather than inventing a schema column (that would be a migration = Rule 4).

type Token

type Token struct {
	// contains filtered or unexported fields
}

Token is the in-memory one-time setup credential gating every /setup/* route (T-13-07-SetupToken, amendment #10). It is generated as a random UUIDv4 when the operator left AURA_SETUP_TOKEN empty (printed once to stdout so the operator can read it off the boot log), held in memory only (never written to disk), and Invalidate()d after onboarding completes so a second navigation 401s. Concurrent reads (every request) + the single Invalidate write are guarded by a RWMutex.

func NewToken

func NewToken(configured string, out io.Writer) *Token

NewToken builds the one-time token holder. When configured is non-empty the operator-supplied value is used verbatim. When it is empty a random UUIDv4 is generated and the parseable line `AURA_SETUP_TOKEN=<value>` is printed to out exactly once (out is os.Stdout in production; tests pass a buffer to assert the format). The value lives in memory only — it is never persisted.

func (*Token) Invalidate

func (t *Token) Invalidate()

Invalidate burns the token after onboarding completes: every subsequent Valid returns false (the wizard's one-time contract — a second navigation 401s). It is idempotent.

func (*Token) Valid

func (t *Token) Valid(presented string) bool

Valid reports whether presented matches the live token using a constant-time compare (defends against a timing oracle on the gate, golang-security). An invalidated token (post-onboarding) matches nothing.

type TokenRequest

type TokenRequest struct {
	Token string `json:"token"`
}

TokenRequest is the POST /setup/token body: the Telegram Bot API token to validate via getMe. It is a secret — it is never logged and never echoed back.

type TokenResponse

type TokenResponse struct {
	OK          bool   `json:"ok"`
	BotUsername string `json:"bot_username"`
}

TokenResponse is the POST /setup/token result: getMe succeeded and the bot username (the only non-secret getMe field surfaced) is returned so the wizard can build the t.me/<bot> deep-link.

Jump to

Keyboard shortcuts

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