mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 15:26:20 +00:00
fix: included mfa login flow
This commit is contained in:
+2
-2
@@ -21,7 +21,7 @@ require (
|
|||||||
github.com/spf13/viper v1.8.1
|
github.com/spf13/viper v1.8.1
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.0
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||||
golang.org/x/term v0.5.0
|
golang.org/x/term v0.9.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -60,7 +60,7 @@ require (
|
|||||||
go.mongodb.org/mongo-driver v1.10.0 // indirect
|
go.mongodb.org/mongo-driver v1.10.0 // indirect
|
||||||
golang.org/x/net v0.7.0 // indirect
|
golang.org/x/net v0.7.0 // indirect
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
golang.org/x/sys v0.5.0 // indirect
|
golang.org/x/sys v0.9.0 // indirect
|
||||||
golang.org/x/text v0.7.0 // indirect
|
golang.org/x/text v0.7.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
|||||||
@@ -531,9 +531,13 @@ golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||||
|
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
||||||
|
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|||||||
+225
-207
@@ -7,6 +7,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/crypto/argon2"
|
"golang.org/x/crypto/argon2"
|
||||||
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
type params struct {
|
type params struct {
|
||||||
@@ -45,6 +47,7 @@ type params struct {
|
|||||||
const ADD_USER = "Add a new account login"
|
const ADD_USER = "Add a new account login"
|
||||||
const REPLACE_USER = "Override current logged in user"
|
const REPLACE_USER = "Override current logged in user"
|
||||||
const EXIT_USER_MENU = "Exit"
|
const EXIT_USER_MENU = "Exit"
|
||||||
|
const QUIT_BROWSER_LOGIN = "q"
|
||||||
|
|
||||||
// loginCmd represents the login command
|
// loginCmd represents the login command
|
||||||
var loginCmd = &cobra.Command{
|
var loginCmd = &cobra.Command{
|
||||||
@@ -96,202 +99,25 @@ var loginCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
//call browser login function
|
||||||
email string
|
fmt.Printf("\nLogging in via browser... Hit '%s' to cancel\n", QUIT_BROWSER_LOGIN)
|
||||||
password string
|
userCredentialsToBeStored, err := browserCliLogin()
|
||||||
loginOneResponse *api.GetLoginOneV2Response
|
|
||||||
loginTwoResponse *api.GetLoginTwoV2Response
|
|
||||||
)
|
|
||||||
|
|
||||||
//check for --browser flag
|
|
||||||
browserLogin, err := cmd.Flags().GetBool("browser")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse browser flag")
|
//default to cli login on error
|
||||||
|
cliDefaultLogin(&userCredentialsToBeStored)
|
||||||
}
|
}
|
||||||
|
|
||||||
if browserLogin {
|
err = util.StoreUserCredsInKeyRing(&userCredentialsToBeStored)
|
||||||
log.Debug().Msg("Login via browser")
|
|
||||||
//call browser login function
|
|
||||||
loginResponse, err := browserCliLogin()
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, err.Error())
|
|
||||||
}
|
|
||||||
email = loginResponse.Email
|
|
||||||
password = loginResponse.Password
|
|
||||||
loginOneResponse = &loginResponse.LoginOneResponse
|
|
||||||
loginTwoResponse = &loginResponse.LoginTwoResponse
|
|
||||||
//util.HandleError(errors.New("not implemented"), " login via browser not fully implemented")
|
|
||||||
} else {
|
|
||||||
email, password, err = askForLoginCredentials()
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "Unable to parse email and password for authentication")
|
|
||||||
}
|
|
||||||
|
|
||||||
loginOneResponse, loginTwoResponse, err = getFreshUserCredentials(email, password)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Unable to authenticate with the provided credentials, please try again")
|
|
||||||
log.Debug().Err(err)
|
|
||||||
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, verification 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 2FA verification 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 {
|
|
||||||
log.Debug().Msg("Login version 1")
|
|
||||||
encryptedPrivateKey, _ := base64.StdEncoding.DecodeString(loginTwoResponse.EncryptedPrivateKey)
|
|
||||||
tag, err := base64.StdEncoding.DecodeString(loginTwoResponse.Tag)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
IV, err := base64.StdEncoding.DecodeString(loginTwoResponse.Iv)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
paddedPassword := fmt.Sprintf("%032s", password)
|
|
||||||
key := []byte(paddedPassword)
|
|
||||||
|
|
||||||
computedDecryptedPrivateKey, err := crypto.DecryptSymmetric(key, encryptedPrivateKey, tag, IV)
|
|
||||||
if err != nil || len(computedDecryptedPrivateKey) == 0 {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
decryptedPrivateKey = computedDecryptedPrivateKey
|
|
||||||
|
|
||||||
} else if loginTwoResponse.EncryptionVersion == 2 {
|
|
||||||
log.Debug().Msg("Login version 2")
|
|
||||||
protectedKey, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKey)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
protectedKeyTag, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKeyTag)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
protectedKeyIV, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKeyIV)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
nonProtectedTag, err := base64.StdEncoding.DecodeString(loginTwoResponse.Tag)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
nonProtectedIv, err := base64.StdEncoding.DecodeString(loginTwoResponse.Iv)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters := ¶ms{
|
|
||||||
memory: 64 * 1024,
|
|
||||||
iterations: 3,
|
|
||||||
parallelism: 1,
|
|
||||||
keyLength: 32,
|
|
||||||
}
|
|
||||||
|
|
||||||
derivedKey, err := generateFromPassword(password, []byte(loginOneResponse.Salt), parameters)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(fmt.Errorf("unable to generate argon hash from password [err=%s]", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
decryptedProtectedKey, err := crypto.DecryptSymmetric(derivedKey, protectedKey, protectedKeyTag, protectedKeyIV)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(fmt.Errorf("unable to get decrypted protected key [err=%s]", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptedPrivateKey, err := base64.StdEncoding.DecodeString(loginTwoResponse.EncryptedPrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
decryptedProtectedKeyInHex, err := hex.DecodeString(string(decryptedProtectedKey))
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
computedDecryptedPrivateKey, err := crypto.DecryptSymmetric(decryptedProtectedKeyInHex, encryptedPrivateKey, nonProtectedTag, nonProtectedIv)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
decryptedPrivateKey = computedDecryptedPrivateKey
|
|
||||||
} else {
|
|
||||||
util.PrintErrorMessageAndExit("Insufficient details to decrypt private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(decryptedPrivateKey) == "" || email == "" || loginTwoResponse.Token == "" {
|
|
||||||
log.Debug().Msgf("[decryptedPrivateKey=%s] [email=%s] [loginTwoResponse.Token=%s]", string(decryptedPrivateKey), email, loginTwoResponse.Token)
|
|
||||||
util.PrintErrorMessageAndExit("We were unable to fetch required details to complete your login. Run with -d to see more info")
|
|
||||||
}
|
|
||||||
|
|
||||||
userCredentialsToBeStored := &models.UserCredentials{
|
|
||||||
Email: email,
|
|
||||||
PrivateKey: string(decryptedPrivateKey),
|
|
||||||
JTWToken: loginTwoResponse.Token,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = util.StoreUserCredsInKeyRing(userCredentialsToBeStored)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
currentVault, _ := util.GetCurrentVaultBackend()
|
currentVault, _ := util.GetCurrentVaultBackend()
|
||||||
log.Error().Msgf("Unable to store your credentials in system vault [%s]. Rerun with flag -d to see full logs", currentVault)
|
log.Error().Msgf("Unable to store your credentials in system vault [%s]. Rerun with flag -d to see full logs", currentVault)
|
||||||
log.Error().Msgf("\nTo trouble shoot further, read https://infisical.com/docs/cli/faq")
|
log.Error().Msgf("\nTo trouble shoot further, read https://infisical.com/docs/cli/faq")
|
||||||
log.Debug().Err(err)
|
log.Debug().Err(err)
|
||||||
return
|
//return here
|
||||||
|
util.HandleError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = util.WriteInitalConfig(userCredentialsToBeStored)
|
err = util.WriteInitalConfig(&userCredentialsToBeStored)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to write write to Infisical Config file. Please try again")
|
util.HandleError(err, "Unable to write write to Infisical Config file. Please try again")
|
||||||
}
|
}
|
||||||
@@ -301,8 +127,9 @@ var loginCmd = &cobra.Command{
|
|||||||
|
|
||||||
whilte := color.New(color.FgGreen)
|
whilte := color.New(color.FgGreen)
|
||||||
boldWhite := whilte.Add(color.Bold)
|
boldWhite := whilte.Add(color.Bold)
|
||||||
|
time.Sleep(time.Second * 1)
|
||||||
boldWhite.Printf(">>>> Welcome to Infisical!")
|
boldWhite.Printf(">>>> Welcome to Infisical!")
|
||||||
boldWhite.Printf(" You are now logged in as %v <<<< \n", email)
|
boldWhite.Printf(" You are now logged in as %v <<<< \n", userCredentialsToBeStored.Email)
|
||||||
|
|
||||||
plainBold := color.New(color.Bold)
|
plainBold := color.New(color.Bold)
|
||||||
|
|
||||||
@@ -313,9 +140,169 @@ var loginCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cliDefaultLogin(userCredentialsToBeStored *models.UserCredentials) {
|
||||||
|
email, password, err := askForLoginCredentials()
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse email and password for authentication")
|
||||||
|
}
|
||||||
|
|
||||||
|
loginOneResponse, loginTwoResponse, err := getFreshUserCredentials(email, password)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Unable to authenticate with the provided credentials, please try again")
|
||||||
|
log.Debug().Err(err)
|
||||||
|
//return here
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, verification 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 2FA verification 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 {
|
||||||
|
log.Debug().Msg("Login version 1")
|
||||||
|
encryptedPrivateKey, _ := base64.StdEncoding.DecodeString(loginTwoResponse.EncryptedPrivateKey)
|
||||||
|
tag, err := base64.StdEncoding.DecodeString(loginTwoResponse.Tag)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
IV, err := base64.StdEncoding.DecodeString(loginTwoResponse.Iv)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
paddedPassword := fmt.Sprintf("%032s", password)
|
||||||
|
key := []byte(paddedPassword)
|
||||||
|
|
||||||
|
computedDecryptedPrivateKey, err := crypto.DecryptSymmetric(key, encryptedPrivateKey, tag, IV)
|
||||||
|
if err != nil || len(computedDecryptedPrivateKey) == 0 {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedPrivateKey = computedDecryptedPrivateKey
|
||||||
|
|
||||||
|
} else if loginTwoResponse.EncryptionVersion == 2 {
|
||||||
|
log.Debug().Msg("Login version 2")
|
||||||
|
protectedKey, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKey)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
protectedKeyTag, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKeyTag)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
protectedKeyIV, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKeyIV)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nonProtectedTag, err := base64.StdEncoding.DecodeString(loginTwoResponse.Tag)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nonProtectedIv, err := base64.StdEncoding.DecodeString(loginTwoResponse.Iv)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters := ¶ms{
|
||||||
|
memory: 64 * 1024,
|
||||||
|
iterations: 3,
|
||||||
|
parallelism: 1,
|
||||||
|
keyLength: 32,
|
||||||
|
}
|
||||||
|
|
||||||
|
derivedKey, err := generateFromPassword(password, []byte(loginOneResponse.Salt), parameters)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(fmt.Errorf("unable to generate argon hash from password [err=%s]", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedProtectedKey, err := crypto.DecryptSymmetric(derivedKey, protectedKey, protectedKeyTag, protectedKeyIV)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(fmt.Errorf("unable to get decrypted protected key [err=%s]", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedPrivateKey, err := base64.StdEncoding.DecodeString(loginTwoResponse.EncryptedPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedProtectedKeyInHex, err := hex.DecodeString(string(decryptedProtectedKey))
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
computedDecryptedPrivateKey, err := crypto.DecryptSymmetric(decryptedProtectedKeyInHex, encryptedPrivateKey, nonProtectedTag, nonProtectedIv)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedPrivateKey = computedDecryptedPrivateKey
|
||||||
|
} else {
|
||||||
|
util.PrintErrorMessageAndExit("Insufficient details to decrypt private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(decryptedPrivateKey) == "" || email == "" || loginTwoResponse.Token == "" {
|
||||||
|
log.Debug().Msgf("[decryptedPrivateKey=%s] [email=%s] [loginTwoResponse.Token=%s]", string(decryptedPrivateKey), email, loginTwoResponse.Token)
|
||||||
|
util.PrintErrorMessageAndExit("We were unable to fetch required details to complete your login. Run with -d to see more info")
|
||||||
|
}
|
||||||
|
|
||||||
|
//updating usercredentials
|
||||||
|
userCredentialsToBeStored.Email = email
|
||||||
|
userCredentialsToBeStored.PrivateKey = string(decryptedPrivateKey)
|
||||||
|
userCredentialsToBeStored.JTWToken = loginTwoResponse.Token
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(loginCmd)
|
rootCmd.AddCommand(loginCmd)
|
||||||
loginCmd.Flags().BoolP("browser", "b", false, "Login via browser")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DomainOverridePrompt() (bool, error) {
|
func DomainOverridePrompt() (bool, error) {
|
||||||
@@ -400,6 +387,7 @@ func askForLoginCredentials() (email string, password string, err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("Enter Credentials...")
|
||||||
emailPrompt := promptui.Prompt{
|
emailPrompt := promptui.Prompt{
|
||||||
Label: "Email",
|
Label: "Email",
|
||||||
Validate: validateEmail,
|
Validate: validateEmail,
|
||||||
@@ -513,21 +501,15 @@ func askForMFACode() string {
|
|||||||
return mfaVerifyCode
|
return mfaVerifyCode
|
||||||
}
|
}
|
||||||
|
|
||||||
type CliLoginResponse struct {
|
// Manages the browser login flow.
|
||||||
Email string `json:"email"`
|
// Returns a UserCredentials object on success and an error on failure
|
||||||
Password string `json:"password"`
|
func browserCliLogin() (models.UserCredentials, error) {
|
||||||
LoginOneResponse api.GetLoginOneV2Response `json:"loginOneResponse"`
|
SERVER_TIMEOUT := 60 * 10
|
||||||
LoginTwoResponse api.GetLoginTwoV2Response `json:"loginTwoResponse"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manages the browser login flow
|
|
||||||
// returns a CLILoginResponse on success and an error on failure
|
|
||||||
func browserCliLogin() (CliLoginResponse, error) {
|
|
||||||
|
|
||||||
//create listener
|
//create listener
|
||||||
listener, err := net.Listen("tcp", ":0")
|
listener, err := net.Listen("tcp", "localhost:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CliLoginResponse{}, err
|
return models.UserCredentials{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//get callback port
|
//get callback port
|
||||||
@@ -537,13 +519,17 @@ func browserCliLogin() (CliLoginResponse, error) {
|
|||||||
//open browser and login
|
//open browser and login
|
||||||
err = browser.OpenURL(url)
|
err = browser.OpenURL(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CliLoginResponse{}, err
|
return models.UserCredentials{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//flow channels
|
//flow channels
|
||||||
success := make(chan CliLoginResponse)
|
success := make(chan models.UserCredentials)
|
||||||
failure := make(chan error)
|
failure := make(chan error)
|
||||||
timeout := time.After(time.Second * 60)
|
timeout := time.After(time.Second * time.Duration(SERVER_TIMEOUT))
|
||||||
|
quit := make(chan bool)
|
||||||
|
|
||||||
|
//
|
||||||
|
done := false
|
||||||
|
|
||||||
//create handler
|
//create handler
|
||||||
c := cors.New(cors.Options{
|
c := cors.New(cors.Options{
|
||||||
@@ -556,21 +542,53 @@ func browserCliLogin() (CliLoginResponse, error) {
|
|||||||
corsHandler := c.Handler(browserLoginHandler(success, failure))
|
corsHandler := c.Handler(browserLoginHandler(success, failure))
|
||||||
|
|
||||||
log.Debug().Msgf("Callback server listening on port %d", callbackPort)
|
log.Debug().Msgf("Callback server listening on port %d", callbackPort)
|
||||||
|
go quitBrowserLogin(quit, &done)
|
||||||
go http.Serve(listener, corsHandler)
|
go http.Serve(listener, corsHandler)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case loginResponse := <-success:
|
case loginResponse := <-success:
|
||||||
err = closeListener(&listener)
|
err = closeListener(&listener)
|
||||||
|
done = true
|
||||||
return loginResponse, nil
|
return loginResponse, nil
|
||||||
|
|
||||||
case err = <-failure:
|
case err = <-failure:
|
||||||
err = closeListener(&listener)
|
err = closeListener(&listener)
|
||||||
return CliLoginResponse{}, err
|
done = true
|
||||||
|
return models.UserCredentials{}, err
|
||||||
|
|
||||||
case _ = <-timeout:
|
case _ = <-timeout:
|
||||||
err = closeListener(&listener)
|
err = closeListener(&listener)
|
||||||
return CliLoginResponse{}, errors.New("server timeout")
|
done = true
|
||||||
|
return models.UserCredentials{}, errors.New("server timeout")
|
||||||
|
|
||||||
|
case _ = <-quit:
|
||||||
|
return models.UserCredentials{}, errors.New("quitting browser login, defaulting to cli...")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// listens to 'q' input on terminal and
|
||||||
|
// sends 'true' to 'quit' channel
|
||||||
|
func quitBrowserLogin(quit chan bool, done *bool) {
|
||||||
|
//
|
||||||
|
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer term.Restore(int(os.Stdin.Fd()), oldState)
|
||||||
|
b := make([]byte, 1)
|
||||||
|
for {
|
||||||
|
_, _ = os.Stdin.Read(b)
|
||||||
|
if string(b) == QUIT_BROWSER_LOGIN {
|
||||||
|
quit <- true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if *done {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -584,10 +602,10 @@ func closeListener(listener *net.Listener) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func browserLoginHandler(success chan CliLoginResponse, failure chan error) http.HandlerFunc {
|
func browserLoginHandler(success chan models.UserCredentials, failure chan error) http.HandlerFunc {
|
||||||
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var loginResponse CliLoginResponse
|
var loginResponse models.UserCredentials
|
||||||
|
|
||||||
decoder := json.NewDecoder(r.Body)
|
decoder := json.NewDecoder(r.Body)
|
||||||
err := decoder.Decode(&loginResponse)
|
err := decoder.Decode(&loginResponse)
|
||||||
|
|||||||
@@ -49,12 +49,20 @@ export default function InitialLoginStep({
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (isCliLoginSuccessful && isCliLoginSuccessful.success) {
|
if (isCliLoginSuccessful && isCliLoginSuccessful.success) {
|
||||||
|
|
||||||
|
if (isCliLoginSuccessful.mfaEnabled) {
|
||||||
|
// case: login requires MFA step
|
||||||
|
setStep(2);
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// case: login was successful
|
// case: login was successful
|
||||||
const cliUrl = `http://localhost:${callbackPort}`
|
const cliUrl = `http://localhost:${callbackPort}`
|
||||||
|
|
||||||
//send request to server endpoint
|
//send request to server endpoint
|
||||||
const instance = axios.create()
|
const instance = axios.create()
|
||||||
const cliResp = await instance.post(cliUrl,{...isCliLoginSuccessful.loginResponse,email,password})
|
const cliResp = await instance.post(cliUrl,{...isCliLoginSuccessful.loginResponse})
|
||||||
|
console.log(cliResp)
|
||||||
|
|
||||||
//cli page
|
//cli page
|
||||||
router.push("/cli-redirect");
|
router.push("/cli-redirect");
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import React, { useState } from 'react';
|
|||||||
import ReactCodeInput from 'react-code-input';
|
import ReactCodeInput from 'react-code-input';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
import attemptLoginMfa from '@app/components/utilities/attemptLoginMfa';
|
import attemptLoginMfa from '@app/components/utilities/attemptLoginMfa';
|
||||||
|
import attemptCliLoginMfa from '@app/components/utilities/attemptCliLoginMfa'
|
||||||
import { useSendMfaToken } from '@app/hooks/api/auth';
|
import { useSendMfaToken } from '@app/hooks/api/auth';
|
||||||
|
|
||||||
import Error from '../basic/Error';
|
import Error from '../basic/Error';
|
||||||
@@ -76,17 +78,43 @@ export default function MFAStep({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const isLoginSuccessful = await attemptLoginMfa({
|
const queryParams = new URLSearchParams(location.search)
|
||||||
email,
|
if (queryParams){
|
||||||
password,
|
const callbackPort = queryParams.get("callback_port")
|
||||||
providerAuthToken,
|
|
||||||
mfaToken: mfaCode
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isLoginSuccessful) {
|
//attemptCliLogin
|
||||||
setIsLoading(false);
|
const isCliLoginSuccessful = await attemptCliLoginMfa({
|
||||||
router.push(`/dashboard/${localStorage.getItem('projectData.id')}`);
|
email,
|
||||||
|
password,
|
||||||
|
providerAuthToken,
|
||||||
|
mfaToken: mfaCode
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isCliLoginSuccessful && isCliLoginSuccessful.success){
|
||||||
|
// case: login was successful
|
||||||
|
const cliUrl = `http://localhost:${callbackPort}`
|
||||||
|
|
||||||
|
//send request to server endpoint
|
||||||
|
const instance = axios.create()
|
||||||
|
const cliResp = await instance.post(cliUrl,{...isCliLoginSuccessful.loginResponse,email})
|
||||||
|
|
||||||
|
//cli page
|
||||||
|
router.push("/cli-redirect");
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
const isLoginSuccessful = await attemptLoginMfa({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
providerAuthToken,
|
||||||
|
mfaToken: mfaCode
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoginSuccessful) {
|
||||||
|
setIsLoading(false);
|
||||||
|
router.push(`/dashboard/${localStorage.getItem('projectData.id')}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = err as VerifyMfaTokenError;
|
const error = err as VerifyMfaTokenError;
|
||||||
|
|
||||||
|
|||||||
@@ -15,23 +15,11 @@ import SecurityClient from './SecurityClient';
|
|||||||
const client = new jsrp.client();
|
const client = new jsrp.client();
|
||||||
|
|
||||||
interface IsCliLoginSuccessful {
|
interface IsCliLoginSuccessful {
|
||||||
loginResponse: {
|
mfaEnabled: boolean;
|
||||||
loginOneResponse: {
|
loginResponse?: {
|
||||||
serverPublicKey: string;
|
email: string;
|
||||||
salt: string;
|
privateKey: string;
|
||||||
};
|
JTWToken: string;
|
||||||
loginTwoResponse: {
|
|
||||||
mfaEnabled: boolean;
|
|
||||||
token: string;
|
|
||||||
encryptionVersion?: number;
|
|
||||||
protectedKey?: string;
|
|
||||||
protectedKeyIV?: string;
|
|
||||||
protectedKeyTag?: string;
|
|
||||||
publicKey?: string;
|
|
||||||
encryptedPrivateKey?: string;
|
|
||||||
iv?: string;
|
|
||||||
tag?: string;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
success: boolean;
|
success: boolean;
|
||||||
}
|
}
|
||||||
@@ -92,27 +80,54 @@ const attemptLogin = async (
|
|||||||
providerAuthToken,
|
providerAuthToken,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
if (mfaEnabled) {
|
||||||
|
// case: MFA is enabled
|
||||||
|
|
||||||
resolve({
|
// set temporary (MFA) JWT token
|
||||||
loginResponse: {
|
SecurityClient.setMfaToken(token);
|
||||||
loginOneResponse: { serverPublicKey, salt },
|
|
||||||
loginTwoResponse: {
|
|
||||||
mfaEnabled,
|
|
||||||
encryptionVersion,
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
token,
|
|
||||||
publicKey,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
iv,
|
|
||||||
tag
|
|
||||||
}
|
|
||||||
},
|
|
||||||
success: true
|
|
||||||
})
|
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
mfaEnabled,
|
||||||
|
success: true
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
!mfaEnabled &&
|
||||||
|
encryptionVersion &&
|
||||||
|
encryptedPrivateKey &&
|
||||||
|
iv &&
|
||||||
|
tag &&
|
||||||
|
token
|
||||||
|
) {
|
||||||
|
// case: MFA is not enabled
|
||||||
|
|
||||||
|
// unset provider auth token in case it was used
|
||||||
|
SecurityClient.setProviderAuthToken('');
|
||||||
|
// set JWT token
|
||||||
|
SecurityClient.setToken(token);
|
||||||
|
|
||||||
|
const privateKey = await KeyService.decryptPrivateKey({
|
||||||
|
encryptionVersion,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
password,
|
||||||
|
salt,
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
mfaEnabled: false,
|
||||||
|
loginResponse: {
|
||||||
|
email: email,
|
||||||
|
privateKey: privateKey,
|
||||||
|
JTWToken: token
|
||||||
|
},
|
||||||
|
success: true
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
/* eslint-disable prefer-destructuring */
|
||||||
|
import jsrp from 'jsrp';
|
||||||
|
|
||||||
|
import login1 from '@app/pages/api/auth/Login1';
|
||||||
|
import verifyMfaToken from '@app/pages/api/auth/verifyMfaToken';
|
||||||
|
import getOrganizations from '@app/pages/api/organization/getOrgs';
|
||||||
|
import getOrganizationUserProjects from '@app/pages/api/organization/GetOrgUserProjects';
|
||||||
|
import KeyService from '@app/services/KeyService';
|
||||||
|
|
||||||
|
import { saveTokenToLocalStorage } from './saveTokenToLocalStorage';
|
||||||
|
import SecurityClient from './SecurityClient';
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const client = new jsrp.client();
|
||||||
|
|
||||||
|
interface isMfaLoginSuccessful {
|
||||||
|
success: boolean;
|
||||||
|
loginResponse:{
|
||||||
|
privateKey: string;
|
||||||
|
JTWToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether or not MFA-login is successful for user with email [email]
|
||||||
|
* and MFA token [mfaToken]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.email - email of user
|
||||||
|
* @param {String} obj.mfaToken - MFA code/token
|
||||||
|
*/
|
||||||
|
const attemptLoginMfa = async ({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
providerAuthToken,
|
||||||
|
mfaToken
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
providerAuthToken?: string,
|
||||||
|
mfaToken: string;
|
||||||
|
}): Promise<isMfaLoginSuccessful> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
client.init({
|
||||||
|
username: email,
|
||||||
|
password
|
||||||
|
}, async () => {
|
||||||
|
try {
|
||||||
|
const clientPublicKey = client.getPublicKey();
|
||||||
|
const { salt } = await login1({
|
||||||
|
email,
|
||||||
|
clientPublicKey,
|
||||||
|
providerAuthToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
encryptionVersion,
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag,
|
||||||
|
token,
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag
|
||||||
|
} = await verifyMfaToken({
|
||||||
|
email,
|
||||||
|
mfaToken
|
||||||
|
});
|
||||||
|
|
||||||
|
// unset temporary (MFA) JWT token and set JWT token
|
||||||
|
SecurityClient.setMfaToken('');
|
||||||
|
SecurityClient.setToken(token);
|
||||||
|
SecurityClient.setProviderAuthToken('');
|
||||||
|
|
||||||
|
const privateKey = await KeyService.decryptPrivateKey({
|
||||||
|
encryptionVersion,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
password,
|
||||||
|
salt,
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag
|
||||||
|
});
|
||||||
|
|
||||||
|
saveTokenToLocalStorage({
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
privateKey
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: in the future - move this logic elsewhere
|
||||||
|
// because this function is about logging the user in
|
||||||
|
// and not initializing the login details
|
||||||
|
const userOrgs = await getOrganizations();
|
||||||
|
const orgId = userOrgs[0]._id;
|
||||||
|
localStorage.setItem('orgData.id', orgId);
|
||||||
|
|
||||||
|
const orgUserProjects = await getOrganizationUserProjects({
|
||||||
|
orgId
|
||||||
|
});
|
||||||
|
localStorage.setItem('projectData.id', orgUserProjects[0]._id);
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
success: true,
|
||||||
|
loginResponse:{
|
||||||
|
privateKey: privateKey,
|
||||||
|
JTWToken: token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default attemptLoginMfa;
|
||||||
Reference in New Issue
Block a user