diff --git a/cli/packages/api/api.go b/cli/packages/api/api.go index 637fc1065..f0a347800 100644 --- a/cli/packages/api/api.go +++ b/cli/packages/api/api.go @@ -148,6 +148,28 @@ func CallLogin1V2(httpClient *resty.Client, request GetLoginOneV2Request) (GetLo return loginOneV2Response, nil } +func CallVerifyMfaToken(httpClient *resty.Client, request VerifyMfaTokenRequest) (*VerifyMfaTokenResponse, *VerifyMfaTokenErrorResponse, error) { + var verifyMfaTokenResponse VerifyMfaTokenResponse + var responseError VerifyMfaTokenErrorResponse + response, err := httpClient. + R(). + SetResult(&verifyMfaTokenResponse). + SetHeader("User-Agent", USER_AGENT). + SetError(&responseError). + SetBody(request). + Post(fmt.Sprintf("%v/v2/auth/mfa/verify", config.INFISICAL_URL)) + + if err != nil { + return nil, nil, fmt.Errorf("CallVerifyMfaToken: Unable to complete api request [err=%s]", err) + } + + if response.IsError() { + return nil, &responseError, nil + } + + return &verifyMfaTokenResponse, nil, nil +} + func CallLogin2V2(httpClient *resty.Client, request GetLoginTwoV2Request) (GetLoginTwoV2Response, error) { var loginTwoV2Response GetLoginTwoV2Response response, err := httpClient. diff --git a/cli/packages/api/model.go b/cli/packages/api/model.go index 5dd20b1df..af8dbc5b4 100644 --- a/cli/packages/api/model.go +++ b/cli/packages/api/model.go @@ -291,3 +291,35 @@ type GetLoginTwoV2Response struct { ProtectedKeyIV string `json:"protectedKeyIV"` ProtectedKeyTag string `json:"protectedKeyTag"` } + +type VerifyMfaTokenRequest struct { + Email string `json:"email"` + MFAToken string `json:"mfaToken"` +} + +type VerifyMfaTokenResponse struct { + EncryptionVersion int `json:"encryptionVersion"` + Token string `json:"token"` + PublicKey string `json:"publicKey"` + EncryptedPrivateKey string `json:"encryptedPrivateKey"` + Iv string `json:"iv"` + Tag string `json:"tag"` + ProtectedKey string `json:"protectedKey"` + ProtectedKeyIV string `json:"protectedKeyIV"` + ProtectedKeyTag string `json:"protectedKeyTag"` +} + +type VerifyMfaTokenErrorResponse struct { + Type string `json:"type"` + Message string `json:"message"` + Context struct { + Code string `json:"code"` + TriesLeft int `json:"triesLeft"` + } `json:"context"` + Level int `json:"level"` + LevelName string `json:"level_name"` + StatusCode int `json:"status_code"` + DatetimeIso time.Time `json:"datetime_iso"` + Application string `json:"application"` + Extra []interface{} `json:"extra"` +} diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index e13756b54..e09253407 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -70,6 +70,53 @@ var loginCmd = &cobra.Command{ return } + if loginTwoResponse.MfaEnabled { + i := 1 + for i < 6 { + mfaVerifyCode := askForMFACode() + + httpClient := resty.New() + httpClient.SetAuthToken(loginTwoResponse.Token) + verifyMFAresponse, mfaErrorResponse, requestError := api.CallVerifyMfaToken(httpClient, api.VerifyMfaTokenRequest{ + Email: email, + MFAToken: mfaVerifyCode, + }) + + if requestError != nil { + util.HandleError(err) + break + } else if mfaErrorResponse != nil { + if mfaErrorResponse.Context.Code == "mfa_invalid" { + msg := fmt.Sprintf("Incorrect, MFA code. You have %v attempts left", 5-i) + fmt.Println(msg) + if i == 5 { + util.PrintErrorMessageAndExit("No tries left, please try again in a bit") + break + } + } + + if mfaErrorResponse.Context.Code == "mfa_expired" { + util.PrintErrorMessageAndExit("Your MFA code has expired, please try logging in again") + break + } + i++ + } else { + loginTwoResponse.EncryptedPrivateKey = verifyMFAresponse.EncryptedPrivateKey + loginTwoResponse.EncryptionVersion = verifyMFAresponse.EncryptionVersion + loginTwoResponse.Iv = verifyMFAresponse.Iv + loginTwoResponse.ProtectedKey = verifyMFAresponse.ProtectedKey + loginTwoResponse.ProtectedKeyIV = verifyMFAresponse.ProtectedKeyIV + loginTwoResponse.ProtectedKeyTag = verifyMFAresponse.ProtectedKeyTag + loginTwoResponse.PublicKey = verifyMFAresponse.PublicKey + loginTwoResponse.Tag = verifyMFAresponse.Tag + loginTwoResponse.Token = verifyMFAresponse.Token + loginTwoResponse.EncryptionVersion = verifyMFAresponse.EncryptionVersion + + break + } + } + } + var decryptedPrivateKey []byte if loginTwoResponse.EncryptionVersion == 1 { @@ -290,3 +337,16 @@ func generateFromPassword(password string, salt []byte, p *params) (hash []byte, hash = argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength) return hash, nil } + +func askForMFACode() string { + mfaCodePromptUI := promptui.Prompt{ + Label: "MFA verification code", + } + + mfaVerifyCode, err := mfaCodePromptUI.Run() + if err != nil { + util.HandleError(err) + } + + return mfaVerifyCode +}