package auth import ( "context" "fmt" "net/url" "time" "github.com/hashicorp/vault/api" ) func init() { register(ldapMethod{}) } type ldapMethod struct{} func (ldapMethod) Name() string { return "ldap" } func (ldapMethod) DisplayName() string { return "LDAP" } func (ldapMethod) DefaultMount() string { return "ldap" } func (ldapMethod) Fields() []Field { return []Field{ {Name: "username", Label: "Username", Kind: FieldText, Required: true, EnvFallback: []string{"VAULT_USERNAME", "LOGNAME", "USER"}}, {Name: "password", Label: "Password", Kind: FieldSecret, Required: true, EnvFallback: []string{"VAULT_LDAP_PASSWORD", "VAULT_PASSWORD"}}, } } func (ldapMethod) Login(ctx context.Context, c *api.Client, req Request) (*api.Secret, error) { mount := mountOf(req, "ldap") return loginWrite(ctx, c, mount, "login/"+url.PathEscape(req.Creds.Get("username")), map[string]interface{}{"password": req.Creds.Get("password")}) } func init() { register(oktaMethod{}) } // oktaMethod mirrors ldapMethod's request shape but adds an optional // best-effort poll of auth//verify/ for Okta Verify // number-matching, reported via Request.Events so the TUI can show "tap 42 // in Okta Verify". This endpoint is not documented in Vault's public API // reference; treat any poll failure as non-fatal and fall back to waiting // for the original login response. type oktaMethod struct{} func (oktaMethod) Name() string { return "okta" } func (oktaMethod) DisplayName() string { return "Okta" } func (oktaMethod) DefaultMount() string { return "okta" } func (oktaMethod) Description() string { return "Okta username/password, with optional TOTP and Okta Verify push." } func (oktaMethod) Fields() []Field { return []Field{ {Name: "username", Label: "Username", Kind: FieldText, Required: true, EnvFallback: []string{"VAULT_USERNAME", "LOGNAME", "USER"}}, {Name: "password", Label: "Password", Kind: FieldSecret, Required: true, EnvFallback: []string{"VAULT_PASSWORD"}}, {Name: "totp", Label: "TOTP code (optional)", Kind: FieldText}, } } func (oktaMethod) Login(ctx context.Context, c *api.Client, req Request) (*api.Secret, error) { mount := mountOf(req, "okta") data := map[string]interface{}{"password": req.Creds.Get("password")} if totp := req.Creds.Get("totp"); totp != "" { data["totp"] = totp } nonce := randomNonce(12) data["nonce"] = nonce if req.Events != nil { go pollOktaVerify(ctx, c, mount, nonce, req) } req.Emit(Event{Kind: EventStatus, Message: "contacting Okta…"}) return loginWrite(ctx, c, mount, "login/"+url.PathEscape(req.Creds.Get("username")), data) } func pollOktaVerify(ctx context.Context, c *api.Client, mount, nonce string, req Request) { t := time.NewTicker(1 * time.Second) defer t.Stop() p := fmt.Sprintf("auth/%s/verify/%s", mount, nonce) for { select { case <-ctx.Done(): return case <-t.C: sec, err := c.Logical().ReadWithContext(ctx, p) if err != nil || sec == nil || sec.Data == nil { continue // best-effort; the primary login request is the source of truth } if answer, ok := sec.Data["correct_answer"].(string); ok && answer != "" { req.Emit(Event{Kind: EventStatus, Message: fmt.Sprintf("in Okta Verify, tap the number %q", answer)}) return } } } }