go-passbolt-cli/util/client.go
2024-11-29 10:16:33 +01:00

152 lines
3.9 KiB
Go

package util
import (
"bufio"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/passbolt/go-passbolt/api"
"github.com/passbolt/go-passbolt/helper"
"github.com/spf13/viper"
"golang.org/x/term"
)
// ReadPassword reads a Password interactively or via Pipe
func ReadPassword(prompt string) (string, error) {
fd := int(os.Stdin.Fd())
var pass string
if term.IsTerminal(fd) {
fmt.Print(prompt)
inputPass, err := term.ReadPassword(fd)
if err != nil {
return "", err
}
pass = string(inputPass)
} else {
reader := bufio.NewReader(os.Stdin)
s, err := reader.ReadString('\n')
if err != nil {
return "", err
}
pass = s
}
return strings.Replace(pass, "\n", "", 1), nil
}
// GetClient gets a Logged in Passbolt Client
func GetClient(ctx context.Context) (*api.Client, error) {
serverAddress := viper.GetString("serverAddress")
if serverAddress == "" {
return nil, fmt.Errorf("serverAddress is not defined")
}
userPrivateKey := viper.GetString("userPrivateKey")
if userPrivateKey == "" {
return nil, fmt.Errorf("userPrivateKey is not defined")
}
userPassword := viper.GetString("userPassword")
if userPassword == "" {
cliPassword, err := ReadPassword("Enter Password:")
if err != nil {
fmt.Println()
return nil, fmt.Errorf("Reading Password: %w", err)
}
userPassword = cliPassword
fmt.Println()
}
httpClient, err := GetHttpClient()
if err != nil {
return nil, err
}
client, err := api.NewClient(httpClient, "", serverAddress, userPrivateKey, userPassword)
if err != nil {
return nil, fmt.Errorf("Creating Client: %w", err)
}
client.Debug = viper.GetBool("debug")
token := viper.GetString("serverVerifyToken")
encToken := viper.GetString("serverVerifyEncToken")
if token != "" {
err = client.VerifyServer(ctx, token, encToken)
if err != nil {
return nil, fmt.Errorf("Verifing Server: %w", err)
}
}
switch viper.GetString("mfaMode") {
case "interactive-totp":
client.MFACallback = func(ctx context.Context, c *api.Client, res *api.APIResponse) (http.Cookie, error) {
challenge := api.MFAChallenge{}
err := json.Unmarshal(res.Body, &challenge)
if err != nil {
return http.Cookie{}, fmt.Errorf("Parsing MFA Challenge")
}
if challenge.Provider.TOTP == "" {
return http.Cookie{}, fmt.Errorf("Server Provided no TOTP Provider")
}
for i := 0; i < 3; i++ {
var code string
code, err := ReadPassword("Enter TOTP:")
if err != nil {
fmt.Printf("\n")
return http.Cookie{}, fmt.Errorf("Reading TOTP: %w", err)
}
fmt.Printf("\n")
req := api.MFAChallengeResponse{
TOTP: code,
}
var raw *http.Response
raw, _, err = c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "mfa/verify/totp.json", "v2", req, nil)
if err != nil {
if errors.Unwrap(err) != api.ErrAPIResponseErrorStatusCode {
return http.Cookie{}, fmt.Errorf("Doing MFA Challenge Response: %w", err)
}
fmt.Println("TOTP Verification Failed")
} else {
// MFA worked so lets find the cookie and return it
for _, cookie := range raw.Cookies() {
if cookie.Name == "passbolt_mfa" {
return *cookie, nil
}
}
return http.Cookie{}, fmt.Errorf("Unable to find Passbolt MFA Cookie")
}
}
return http.Cookie{}, fmt.Errorf("Failed MFA Challenge 3 times: %w", err)
}
case "noninteractive-totp":
// if new flag is unset, use old flag instead
totpToken := viper.GetString("mfaTotpToken")
if totpToken == "" {
totpToken = viper.GetString("totpToken")
}
totpOffset := viper.GetDuration("mfaTotpOffset")
if totpOffset == time.Duration(0) {
totpOffset = viper.GetDuration("totpOffset")
}
helper.AddMFACallbackTOTP(client, viper.GetUint("mfaRetrys"), viper.GetDuration("mfaDelay"), totpOffset, totpToken)
case "none":
default:
}
err = client.Login(ctx)
if err != nil {
return nil, fmt.Errorf("Logging in: %w", err)
}
return client, nil
}