auth

package
v0.2.0-alpha.7 Latest Latest
Warning

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

Go to latest
Published: Sep 2, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package auth provides credential persistence and renewal for the BuildMax client. It depends on internal/config for the file location and internal/interface/client to exchange a refresh token, and on nothing from the TUI, desktop, or Cobra layers above it.

Index

Constants

View Source
const (
	// StorageKeyring means they are in the operating system's credential store
	// and auth.json holds only metadata.
	StorageKeyring = "keyring"
	// StorageFile means they are in auth.json itself, mode 0600. It is what a
	// machine with no usable credential store gets, and every surface reports
	// it: a guarantee that quietly stops holding is worse than one never made.
	StorageFile = "file"
)

Where a login's two bearer secrets are kept.

Variables

View Source
var ErrLoginExpired = errors.New("login has expired")

ErrLoginExpired means a login is stored but no longer works: its refresh token expired, or the session was revoked.

It is distinct from a deployment that cannot be reached, because the answer is different. An unreachable server may come back; an expired login will not, and the session cannot continue until someone signs in again or returns to local mode. Neither is a reason to quietly use local models: that would send a prompt somewhere the user did not choose. See docs/design/client-modes.md section 8.

Functions

func ArtifactPublisherForSession

func ArtifactPublisherForSession() tool.ArtifactPublisher

ArtifactPublisherForSession returns this session's artifact capability, or nil when it has none.

Being logged in is the whole precondition, and it is answered here at assembly time rather than discovered when the model calls the tool. A session with no server keeps its ordinary local output behaviour, and its agent is never offered a tool that could only fail — see docs/design/unified-artifacts.md section 7.1.

func CanAuthenticate

func CanAuthenticate(serverURL string) error

CanAuthenticate reports whether a call to serverURL could authenticate, without making one.

TokenForServer answers the same question by renewing when it has to, which contacts the server and rotates the stored refresh token. That is the wrong thing for a diagnostic to do: `buildmax doctor` is read-only, and a check that quietly spends a credential is not a check.

func Clear

func Clear(path string) error

Clear removes the credentials file at path and the secrets that belong to it. It is not an error if either is already gone.

func IsLoggedIn

func IsLoggedIn() (bool, error)

IsLoggedIn reports whether valid credentials are present.

func Logout

func Logout() error

Logout clears the stored credentials.

func LogoutAndRevoke

func LogoutAndRevoke() error

LogoutAndRevoke clears the stored credentials and asks the server to revoke the session behind them.

The local file is cleared whether or not the server can be reached. Someone who typed `buildmax logout` on a machine with no network is signed out of that machine; leaving the credentials behind because a call failed would be the opposite of what they asked for. The returned error reports what could not be revoked, and is worth printing but not worth failing on.

func RequireLogin

func RequireLogin() error

RequireLogin returns an error when no valid credentials are present.

func Save

func Save(creds *Credentials, path string) error

Save persists creds: the two secrets go to this machine's credential store, and auth.json keeps everything else.

A machine with no credential store keeps the secrets in auth.json as before. That is a downgrade, so Save records which one happened in the file rather than leaving the reader to infer it from a missing field.

func SaveCredentials

func SaveCredentials(creds *Credentials) error

SaveCredentials persists a login result.

func StorageDescription

func StorageDescription(kind string) string

StorageDescription is what a surface tells someone about their credential storage. The file mode says why it is the file, because "0600 file" on its own reads as a choice rather than as the absence of an alternative.

func TokenForServer

func TokenForServer(serverURL string) (string, error)

TokenForServer returns a usable access token for calls to serverURL, renewing it first if it is spent or nearly so.

It refuses to hand the credential to any other host. A managed model entry names its own server, and settings.yaml is editable by anything running as the user, so a mismatch would otherwise send a BuildMax token — and every prompt that follows — wherever that file said.

Types

type AuthInfo

type AuthInfo struct {
	LoggedIn  bool   `json:"logged_in"`
	ServerURL string `json:"server_url,omitempty"`
	UserID    string `json:"user_id,omitempty"`
	Email     string `json:"email,omitempty"`
	Name      string `json:"name,omitempty"`
	// Storage is StorageKeyring or StorageFile. A surface reports it because
	// the file mode is a downgrade, and one nobody is told about is one nobody
	// can act on.
	Storage string `json:"storage,omitempty"`
}

AuthInfo is the caller-facing authentication view.

func Info

func Info() (AuthInfo, error)

Info returns the current caller-facing auth state.

type Credentials

type Credentials struct {
	ServerURL string `json:"server_url"`
	// Token is the access token. The field keeps its original name so that a
	// credentials file written before refresh tokens existed still loads.
	Token string `json:"token,omitempty"`
	// RefreshToken renews the access token without another login code. Empty
	// means this login ends when Token expires — either an old file or a server
	// that stores no refresh tokens.
	RefreshToken string    `json:"refresh_token,omitempty"`
	UserID       string    `json:"user_id"`
	Email        string    `json:"email"`
	Name         string    `json:"name"`
	SavedAt      time.Time `json:"saved_at"`
	// Storage names where the two tokens came from: StorageKeyring or
	// StorageFile. Load always sets it from the store that answered, so it
	// describes this machine rather than the machine that wrote the file.
	Storage string `json:"storage,omitempty"`
}

Credentials is one login, assembled from both places it is kept: the metadata from auth.json and the two secrets from wherever this machine keeps secrets.

Token and RefreshToken carry `omitempty` because auth.json holds them only in StorageFile mode. Under StorageKeyring they are blanked before the file is written, which is the whole point of the split.

func Load

func Load(path string) (*Credentials, error)

Load reads credentials from path and fills in the secrets from wherever this machine keeps them. If the file does not exist, it returns (nil, nil) — not an error. Any other read or parse failure is an error.

func StoredLogin

func StoredLogin() (*Credentials, error)

StoredLogin returns the credentials on disk whether or not they still work, and nil when there are none.

This is the mode, and it is deliberately not Info(): Info answers "am I signed in" and reports a spent login as signed out, which would make an expired session look like local mode. See ResolveModelSource.

func (*Credentials) IsUsable

func (c *Credentials) IsUsable() bool

IsUsable reports whether these credentials can still authenticate a call, either because the access token is good or because it can be renewed.

This is the question every command actually has: "am I signed in?" — not "is this particular token still fresh?", which stopped being the same question once a refresh token existed.

func (*Credentials) IsValid

func (c *Credentials) IsValid() bool

IsValid returns true when the credentials contain a non-empty, unexpired access token. It parses the exp claim from the token payload without verifying the signature (clients don't have the server secret).

type IssueSession

type IssueSession struct {
	ServerURL string
	TeamID    string
	TeamName  string
	Issue     coreissue.Issue
	Client    tool.IssueClient
}

IssueSession is one local session's link to one team Issue.

It carries what the session must be able to say out loud as well as what it needs to call: which server and team the work came from, and which Issue. Work crossing that boundary should be visible before it crosses, not inferable afterwards from a tool call.

It lasts one run. The durable form is the local Issue bridge's to design.

func OpenIssueSession

func OpenIssueSession(ctx context.Context, issueID string) (*IssueSession, error)

OpenIssueSession scopes this session to one Issue on the server it is signed in to.

Unlike the artifact capability, a failure here is returned rather than swallowed. Artifacts are resolved for every session and their absence just means the session has no server; an Issue is resolved only because someone asked for one by name, and starting anyway against no Issue would be answering a different request than the one made.

func (*IssueSession) ToolClient

func (s *IssueSession) ToolClient() tool.IssueClient

ToolClient is the Issue capability to hand the runtime, or nil when there is no session. Nil registers no tools, which is what a run with no Issue gets.

type ModelSource

type ModelSource struct {
	// ServerURL is the deployment serving these models, empty in local mode.
	ServerURL string
	// Entries is what that deployment offers. Nil in local mode, where the
	// caller uses settings.yaml instead.
	Entries []config.ModelEntry
	// Default is the model name a new session starts with, as the deployment
	// marked it. Empty in local mode, where settings.yaml says.
	Default string
}

ModelSource is where a surface's models come from.

A login is the mode: with one the models are the deployment's and every prompt goes there, without one they are the ones in settings.yaml and each call is made from this machine. The two are never mixed — see docs/design/client-modes.md section 1.

func ResolveModelSource

func ResolveModelSource(ctx context.Context) (ModelSource, error)

ResolveModelSource asks the deployment this machine is signed in to what it offers, or reports local mode when there is no login.

Managed mode fetches eagerly rather than on first use. The list carries each model's context window, which the session needs before it can compact, and a deployment that cannot be reached must fail where the user is still deciding what to do rather than mid-turn. There is no fallback to local models: that would send a prompt somewhere the user did not choose.

func (ModelSource) Managed

func (s ModelSource) Managed() bool

Managed reports whether these models are served by a deployment.

Jump to

Keyboard shortcuts

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