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 ¶
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 ¶
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 ¶
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 ¶
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.
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 ¶
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.
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 ¶
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.