// Package auth defines the auth-method abstraction vault-tui uses for every // login flow. A Method declares only which fields it needs (Fields); the // TUI and headless CLI both render that declaration generically — a // textinput form in the TUI, a TTY prompt or env/flag lookup on the CLI — // so adding a new method never requires UI code (see prefill.go and // registry.go). package auth import ( "context" "time" "github.com/hashicorp/vault/api" ) // FieldKind tells a renderer how to present a field. type FieldKind uint8 const ( FieldText FieldKind = iota FieldSecret FieldBool FieldSelect FieldPath FieldInt ) // Field declares one credential input a Method needs. type Field struct { Name string Label string Help string Kind FieldKind Required bool Default string Options []string // EnvFallback lists env vars consulted during Prefill, in order, before // falling back to config params. Mirrors Vault CLI behaviour (e.g. // VAULT_AUTH_GITHUB_TOKEN-style env overrides). EnvFallback []string // ConfigKey is the key looked up in profile.auth.params. Empty => Name. ConfigKey string // Validate runs before Login and must not have side effects. Validate func(value string) error } // Credentials is a filled-in form: field name -> value. type Credentials map[string]string func (c Credentials) Get(name string) string { return c[name] } func (c Credentials) Has(name string) bool { v, ok := c[name] return ok && v != "" } // Redacted returns a copy safe for logs: every FieldSecret value becomes // "***". This is the only sanctioned way to log a Credentials map. func (c Credentials) Redacted(fields []Field) map[string]string { secret := map[string]bool{} for _, f := range fields { if f.Kind == FieldSecret { secret[f.Name] = true } } out := make(map[string]string, len(c)) for k, v := range c { if secret[k] { out[k] = "***" } else { out[k] = v } } return out } // EventKind classifies out-of-band progress from a long-running login (OIDC // browser wait, Okta push polling). type EventKind uint8 const ( EventStatus EventKind = iota EventOpenURL EventWarning ) // Event is one piece of progress emitted on Request.Events. type Event struct { Kind EventKind Message string URL string } // Request is everything a Method needs to perform one login. type Request struct { Mount string Namespace string Creds Credentials // Events, if non-nil, receives progress notifications. Login must send // non-blockingly (see emit) so a stalled consumer can never deadlock it. Events chan<- Event Timeout time.Duration } // Emit sends a progress event to Request.Events, non-blocking. Safe to // call even when Events is nil (a headless caller that doesn't want // progress) or when nobody is currently draining the channel. func (r Request) Emit(e Event) { if r.Events == nil { return } select { case r.Events <- e: default: } } // Result is the normalised outcome of a login. type Result struct { Secret *api.Secret Token string Accessor string Renewable bool TTL time.Duration Policies []string Namespace string } func (r *Result) String() string { if r == nil { return "" } return "" } func (r *Result) GoString() string { return r.String() } // NewResult normalises an *api.Secret returned by a successful Login. func NewResult(sec *api.Secret, namespace string) (*Result, error) { if sec == nil || sec.Auth == nil { return nil, errNoAuth } ttl, _ := sec.TokenTTL() policies, _ := sec.TokenPolicies() accessor, _ := sec.TokenAccessor() return &Result{ Secret: sec, Token: sec.Auth.ClientToken, Accessor: accessor, Renewable: sec.Auth.Renewable, TTL: ttl, Policies: policies, Namespace: namespace, }, nil } // Method is the single abstraction every auth method implements. // // Contract: Fields() is pure and cheap. Login must respect ctx cancellation // and must never read os.Stdin — interactive prompting is the caller's job // (TUI form or CLI prompt), driven by Fields()/Missing(). Login must not // mutate client's token; token persistence is internal/token's job. type Method interface { Name() string DisplayName() string DefaultMount() string Fields() []Field Login(ctx context.Context, client *api.Client, req Request) (*api.Secret, error) } // Describable is optional; implemented by methods with extra help text. type Describable interface{ Description() string }