fix: included mfa login flow

This commit is contained in:
quinton11
2023-06-16 12:58:00 +00:00
parent 9e9129dd02
commit dd0fdea19f
7 changed files with 449 additions and 254 deletions

View File

@@ -21,7 +21,7 @@ require (
github.com/spf13/viper v1.8.1
github.com/stretchr/testify v1.8.0
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
golang.org/x/term v0.5.0
golang.org/x/term v0.9.0
)
require (
@@ -60,7 +60,7 @@ require (
go.mongodb.org/mongo-driver v1.10.0 // indirect
golang.org/x/net v0.7.0 // 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
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

View File

@@ -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.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.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.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -7,6 +7,7 @@ import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"os"
"strings"
"time"
@@ -32,6 +33,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"golang.org/x/crypto/argon2"
"golang.org/x/term"
)
type params struct {
@@ -45,6 +47,7 @@ type params struct {
const ADD_USER = "Add a new account login"
const REPLACE_USER = "Override current logged in user"
const EXIT_USER_MENU = "Exit"
const QUIT_BROWSER_LOGIN = "q"
// loginCmd represents the login command
var loginCmd = &cobra.Command{
@@ -96,202 +99,25 @@ var loginCmd = &cobra.Command{
}
}
var (
email string
password string
loginOneResponse *api.GetLoginOneV2Response
loginTwoResponse *api.GetLoginTwoV2Response
)
//check for --browser flag
browserLogin, err := cmd.Flags().GetBool("browser")
//call browser login function
fmt.Printf("\nLogging in via browser... Hit '%s' to cancel\n", QUIT_BROWSER_LOGIN)
userCredentialsToBeStored, err := browserCliLogin()
if err != nil {
util.HandleError(err, "Unable to parse browser flag")
//default to cli login on error
cliDefaultLogin(&userCredentialsToBeStored)
}
if browserLogin {
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 := &params{
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)
err = util.StoreUserCredsInKeyRing(&userCredentialsToBeStored)
if err != nil {
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("\nTo trouble shoot further, read https://infisical.com/docs/cli/faq")
log.Debug().Err(err)
return
//return here
util.HandleError(err)
}
err = util.WriteInitalConfig(userCredentialsToBeStored)
err = util.WriteInitalConfig(&userCredentialsToBeStored)
if err != nil {
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)
boldWhite := whilte.Add(color.Bold)
time.Sleep(time.Second * 1)
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)
@@ -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 := &params{
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() {
rootCmd.AddCommand(loginCmd)
loginCmd.Flags().BoolP("browser", "b", false, "Login via browser")
}
func DomainOverridePrompt() (bool, error) {
@@ -400,6 +387,7 @@ func askForLoginCredentials() (email string, password string, err error) {
return nil
}
fmt.Println("Enter Credentials...")
emailPrompt := promptui.Prompt{
Label: "Email",
Validate: validateEmail,
@@ -513,21 +501,15 @@ func askForMFACode() string {
return mfaVerifyCode
}
type CliLoginResponse struct {
Email string `json:"email"`
Password string `json:"password"`
LoginOneResponse api.GetLoginOneV2Response `json:"loginOneResponse"`
LoginTwoResponse api.GetLoginTwoV2Response `json:"loginTwoResponse"`
}
// Manages the browser login flow
// returns a CLILoginResponse on success and an error on failure
func browserCliLogin() (CliLoginResponse, error) {
// Manages the browser login flow.
// Returns a UserCredentials object on success and an error on failure
func browserCliLogin() (models.UserCredentials, error) {
SERVER_TIMEOUT := 60 * 10
//create listener
listener, err := net.Listen("tcp", ":0")
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
return CliLoginResponse{}, err
return models.UserCredentials{}, err
}
//get callback port
@@ -537,13 +519,17 @@ func browserCliLogin() (CliLoginResponse, error) {
//open browser and login
err = browser.OpenURL(url)
if err != nil {
return CliLoginResponse{}, err
return models.UserCredentials{}, err
}
//flow channels
success := make(chan CliLoginResponse)
success := make(chan models.UserCredentials)
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
c := cors.New(cors.Options{
@@ -556,21 +542,53 @@ func browserCliLogin() (CliLoginResponse, error) {
corsHandler := c.Handler(browserLoginHandler(success, failure))
log.Debug().Msgf("Callback server listening on port %d", callbackPort)
go quitBrowserLogin(quit, &done)
go http.Serve(listener, corsHandler)
for {
select {
case loginResponse := <-success:
err = closeListener(&listener)
done = true
return loginResponse, nil
case err = <-failure:
err = closeListener(&listener)
return CliLoginResponse{}, err
done = true
return models.UserCredentials{}, err
case _ = <-timeout:
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
}
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) {
var loginResponse CliLoginResponse
var loginResponse models.UserCredentials
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&loginResponse)

View File

@@ -49,12 +49,20 @@ export default function InitialLoginStep({
})
if (isCliLoginSuccessful && isCliLoginSuccessful.success) {
if (isCliLoginSuccessful.mfaEnabled) {
// case: login requires MFA step
setStep(2);
setIsLoading(false);
return;
}
// 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,password})
const cliResp = await instance.post(cliUrl,{...isCliLoginSuccessful.loginResponse})
console.log(cliResp)
//cli page
router.push("/cli-redirect");

View File

@@ -3,8 +3,10 @@ import React, { useState } from 'react';
import ReactCodeInput from 'react-code-input';
import { useTranslation } from 'react-i18next';
import { useRouter } from 'next/router';
import axios from "axios"
import attemptLoginMfa from '@app/components/utilities/attemptLoginMfa';
import attemptCliLoginMfa from '@app/components/utilities/attemptCliLoginMfa'
import { useSendMfaToken } from '@app/hooks/api/auth';
import Error from '../basic/Error';
@@ -76,17 +78,43 @@ export default function MFAStep({
}
setIsLoading(true);
const isLoginSuccessful = await attemptLoginMfa({
email,
password,
providerAuthToken,
mfaToken: mfaCode
});
const queryParams = new URLSearchParams(location.search)
if (queryParams){
const callbackPort = queryParams.get("callback_port")
if (isLoginSuccessful) {
setIsLoading(false);
router.push(`/dashboard/${localStorage.getItem('projectData.id')}`);
//attemptCliLogin
const isCliLoginSuccessful = await attemptCliLoginMfa({
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) {
const error = err as VerifyMfaTokenError;

View File

@@ -15,23 +15,11 @@ import SecurityClient from './SecurityClient';
const client = new jsrp.client();
interface IsCliLoginSuccessful {
loginResponse: {
loginOneResponse: {
serverPublicKey: string;
salt: string;
};
loginTwoResponse: {
mfaEnabled: boolean;
token: string;
encryptionVersion?: number;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
publicKey?: string;
encryptedPrivateKey?: string;
iv?: string;
tag?: string;
};
mfaEnabled: boolean;
loginResponse?: {
email: string;
privateKey: string;
JTWToken: string;
};
success: boolean;
}
@@ -92,27 +80,54 @@ const attemptLogin = async (
providerAuthToken,
}
);
if (mfaEnabled) {
// case: MFA is enabled
resolve({
loginResponse: {
loginOneResponse: { serverPublicKey, salt },
loginTwoResponse: {
mfaEnabled,
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
token,
publicKey,
encryptedPrivateKey,
iv,
tag
}
},
success: true
})
// set temporary (MFA) JWT token
SecurityClient.setMfaToken(token);
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) {
reject(err);
}

View File

@@ -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;