// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package api import ( "context" "fmt" ) // Auth is used to perform credential backend related operations. type Auth struct { c *Client } type AuthMethod interface { Login(ctx context.Context, client *Client) (*Secret, error) } // Auth is used to return the client for credential-backend API calls. func (c *Client) Auth() *Auth { return &Auth{c: c} } // Login sets up the required request body for login requests to the given auth // method's /login API endpoint, and then performs a write to it. After a // successful login, this method will automatically set the client's token to // the login response's ClientToken as well. // // The Secret returned is the authentication secret, which if desired can be // passed as input to the NewLifetimeWatcher method in order to start // automatically renewing the token. func (a *Auth) Login(ctx context.Context, authMethod AuthMethod) (*Secret, error) { if authMethod == nil { return nil, fmt.Errorf("no auth method provided for login") } return a.login(ctx, authMethod) } // MFALogin is a wrapper that helps satisfy Vault's MFA implementation. // If optional credentials are provided a single-phase login will be attempted // and the resulting Secret will contain a ClientToken if the authentication is successful. // The client's token will also be set accordingly. // // If no credentials are provided a two-phase MFA login will be assumed and the resulting // Secret will have a MFARequirement containing the MFARequestID to be used in a follow-up // call to `sys/mfa/validate` or by passing it to the method (*Auth).MFAValidate. func (a *Auth) MFALogin(ctx context.Context, authMethod AuthMethod, creds ...string) (*Secret, error) { if len(creds) > 0 { a.c.SetMFACreds(creds) return a.login(ctx, authMethod) } return a.twoPhaseMFALogin(ctx, authMethod) } // MFAValidate validates an MFA request using the appropriate payload and a secret containing // Auth.MFARequirement, like the one returned by MFALogin when credentials are not provided. // Upon successful validation the client token will be set accordingly. // // The Secret returned is the authentication secret, which if desired can be // passed as input to the NewLifetimeWatcher method in order to start // automatically renewing the token. func (a *Auth) MFAValidate(ctx context.Context, mfaSecret *Secret, payload map[string]interface{}) (*Secret, error) { if mfaSecret == nil || mfaSecret.Auth == nil || mfaSecret.Auth.MFARequirement == nil { return nil, fmt.Errorf("secret does not contain MFARequirements") } s, err := a.c.Sys().MFAValidateWithContext(ctx, mfaSecret.Auth.MFARequirement.MFARequestID, payload) if err != nil { return nil, err } return a.checkAndSetToken(s) } // login performs the (*AuthMethod).Login() with the configured client and checks that a ClientToken is returned func (a *Auth) login(ctx context.Context, authMethod AuthMethod) (*Secret, error) { s, err := authMethod.Login(ctx, a.c) if err != nil { return nil, fmt.Errorf("unable to log in to auth method: %w", err) } return a.checkAndSetToken(s) } // twoPhaseMFALogin performs the (*AuthMethod).Login() with the configured client // and checks that an MFARequirement is returned func (a *Auth) twoPhaseMFALogin(ctx context.Context, authMethod AuthMethod) (*Secret, error) { s, err := authMethod.Login(ctx, a.c) if err != nil { return nil, fmt.Errorf("unable to log in: %w", err) } if s == nil || s.Auth == nil || s.Auth.MFARequirement == nil { if s != nil { s.Warnings = append(s.Warnings, "expected secret to contain MFARequirements") } return s, fmt.Errorf("assumed two-phase MFA login, returned secret is missing MFARequirements") } return s, nil } func (a *Auth) checkAndSetToken(s *Secret) (*Secret, error) { if s == nil || s.Auth == nil || s.Auth.ClientToken == "" { if s != nil { s.Warnings = append(s.Warnings, "expected secret to contain ClientToken") } return s, fmt.Errorf("response did not return ClientToken, client token not set") } a.c.SetToken(s.Auth.ClientToken) return s, nil }