package auth import ( "fmt" "os" "strings" ) // EnvAuthParamPrefix + strings.ToUpper(Field.Name) is consulted by Prefill // alongside each Field's own EnvFallback list, so any field can be // overridden from the environment even if the method author didn't think // to name a specific var for it. const EnvAuthParamPrefix = "VAULT_TUI_AUTH_" // PrefillSource is everything Prefill draws from, ordered highest priority // first: CLIArgs > env fallback / VAULT_TUI_AUTH_* > ConfigArgs > Field.Default. type PrefillSource struct { CLIArgs map[string]string // `vault-tui login -method=x role=eng` ConfigArgs map[string]string // profile.auth.params LookupEnv func(string) (string, bool) } func (s PrefillSource) lookupEnv(key string) (string, bool) { if s.LookupEnv != nil { return s.LookupEnv(key) } return os.LookupEnv(key) } // Prefill resolves each of m's fields' initial value from src, in // precedence order: CLIArgs > Field.EnvFallback > VAULT_TUI_AUTH_ > // ConfigArgs > Field.Default. func Prefill(m Method, src PrefillSource) Credentials { out := Credentials{} for _, f := range m.Fields() { key := f.ConfigKey if key == "" { key = f.Name } if v, ok := src.CLIArgs[f.Name]; ok && v != "" { out[f.Name] = v continue } found := false for _, envKey := range f.EnvFallback { if v, ok := src.lookupEnv(envKey); ok && v != "" { out[f.Name] = v found = true break } } if found { continue } if v, ok := src.lookupEnv(EnvAuthParamPrefix + strings.ToUpper(f.Name)); ok && v != "" { out[f.Name] = v continue } if v, ok := src.ConfigArgs[key]; ok && v != "" { out[f.Name] = v continue } if f.Default != "" { out[f.Name] = f.Default } } return out } // Missing returns the Required fields that are still empty after Prefill. // The TUI renders a form for exactly these; a headless, TTY-attached CLI // prompts for them; a headless, non-TTY CLI should treat a non-empty result // as a hard error (never block on stdin). func Missing(m Method, creds Credentials) []Field { var out []Field for _, f := range m.Fields() { if f.Required && !creds.Has(f.Name) { out = append(out, f) } } return out } // Validate runs every Field.Validate and checks Required, returning the // first error encountered. func Validate(m Method, creds Credentials) error { for _, f := range m.Fields() { v, ok := creds[f.Name] if f.Required && (!ok || v == "") { return fmt.Errorf("missing required field %q", f.Name) } if ok && f.Validate != nil { if err := f.Validate(v); err != nil { return fmt.Errorf("field %q: %w", f.Name, err) } } } return nil }