Merge pull request #2175 from akhilmhdh/feat/cli-login-fallback-missing

Missing paste token option in CLI brower login flow
This commit is contained in:
Maidul Islam
2024-07-25 10:08:57 -04:00
committed by GitHub
3 changed files with 58 additions and 27 deletions
+4 -8
View File
@@ -24,7 +24,6 @@ import (
"github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/models"
"github.com/Infisical/infisical-merge/packages/srp" "github.com/Infisical/infisical-merge/packages/srp"
"github.com/Infisical/infisical-merge/packages/util" "github.com/Infisical/infisical-merge/packages/util"
"github.com/chzyer/readline"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/manifoldco/promptui" "github.com/manifoldco/promptui"
@@ -205,6 +204,7 @@ var loginCmd = &cobra.Command{
if !overrideDomain { if !overrideDomain {
domainQuery = false domainQuery = false
config.INFISICAL_URL = util.AppendAPIEndpoint(config.INFISICAL_URL_MANUAL_OVERRIDE) config.INFISICAL_URL = util.AppendAPIEndpoint(config.INFISICAL_URL_MANUAL_OVERRIDE)
config.INFISICAL_LOGIN_URL = fmt.Sprintf("%s/login", strings.TrimSuffix(config.INFISICAL_URL, "/api"))
} }
} }
@@ -713,7 +713,7 @@ func askForMFACode() string {
return mfaVerifyCode return mfaVerifyCode
} }
func askToPasteJwtToken(stdin *readline.CancelableStdin, success chan models.UserCredentials, failure chan error) { func askToPasteJwtToken(success chan models.UserCredentials, failure chan error) {
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)
fmt.Println("\n\nOnce login is completed via browser, the CLI should be authenticated automatically.") fmt.Println("\n\nOnce login is completed via browser, the CLI should be authenticated automatically.")
fmt.Println("However, if browser fails to communicate with the CLI, please paste the token from the browser below.") fmt.Println("However, if browser fails to communicate with the CLI, please paste the token from the browser below.")
@@ -807,26 +807,22 @@ func browserCliLogin() (models.UserCredentials, error) {
log.Debug().Msgf("Callback server listening on port %d", callbackPort) log.Debug().Msgf("Callback server listening on port %d", callbackPort)
stdin := readline.NewCancelableStdin(os.Stdin)
go http.Serve(listener, corsHandler) go http.Serve(listener, corsHandler)
go askToPasteJwtToken(stdin, success, failure) go askToPasteJwtToken(success, failure)
for { for {
select { select {
case loginResponse := <-success: case loginResponse := <-success:
_ = closeListener(&listener) _ = closeListener(&listener)
_ = stdin.Close()
fmt.Println("Browser login successful") fmt.Println("Browser login successful")
return loginResponse, nil return loginResponse, nil
case err := <-failure: case err := <-failure:
serverErr := closeListener(&listener) serverErr := closeListener(&listener)
stdErr := stdin.Close() return models.UserCredentials{}, errors.Join(err, serverErr)
return models.UserCredentials{}, errors.Join(err, serverErr, stdErr)
case <-timeout: case <-timeout:
_ = closeListener(&listener) _ = closeListener(&listener)
_ = stdin.Close()
return models.UserCredentials{}, errors.New("server timeout") return models.UserCredentials{}, errors.New("server timeout")
} }
} }
@@ -168,16 +168,27 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => {
const { token: newJwtToken } = await selectOrganization({ organizationId }); const { token: newJwtToken } = await selectOrganization({ organizationId });
const instance = axios.create(); const instance = axios.create();
await instance.post(cliUrl, { const payload = {
...isCliLoginSuccessful.loginResponse, ...isCliLoginSuccessful.loginResponse,
JTWToken: newJwtToken JTWToken: newJwtToken
};
await instance.post(cliUrl, payload).catch(() => {
// if error happens to communicate we set the token with an expiry in sessino storage
// the cli-redirect page has logic to show this to user and ask them to paste it in terminal
sessionStorage.setItem(
SessionStorageKeys.CLI_TERMINAL_TOKEN,
JSON.stringify({
expiry: formatISO(addSeconds(new Date(), 30)),
data: window.btoa(JSON.stringify(payload))
})
);
}); });
router.push("/cli-redirect");
await navigateUserToOrg(router, organizationId); return;
} }
// case: no organization ID is present -- navigate to the select org page IF the user has any orgs // case: no organization ID is present -- navigate to the select org page IF the user has any orgs
// if the user has no orgs, navigate to the create org page // if the user has no orgs, navigate to the create org page
else {
const userOrgs = await fetchOrganizations(); const userOrgs = await fetchOrganizations();
// case: user has orgs, so we navigate the user to select an org // case: user has orgs, so we navigate the user to select an org
@@ -189,7 +200,7 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => {
else { else {
await navigateUserToOrg(router); await navigateUserToOrg(router);
} }
}
} }
} else { } else {
const isLoginSuccessful = await attemptLoginMfa({ const isLoginSuccessful = await attemptLoginMfa({
@@ -4,6 +4,7 @@ import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import HCaptcha from "@hcaptcha/react-hcaptcha"; import HCaptcha from "@hcaptcha/react-hcaptcha";
import axios from "axios"; import axios from "axios";
import { addSeconds, formatISO } from "date-fns";
import jwt_decode from "jwt-decode"; import jwt_decode from "jwt-decode";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
@@ -12,6 +13,7 @@ import attemptLogin from "@app/components/utilities/attemptLogin";
import { CAPTCHA_SITE_KEY } from "@app/components/utilities/config"; import { CAPTCHA_SITE_KEY } from "@app/components/utilities/config";
import SecurityClient from "@app/components/utilities/SecurityClient"; import SecurityClient from "@app/components/utilities/SecurityClient";
import { Button, Input, Spinner } from "@app/components/v2"; import { Button, Input, Spinner } from "@app/components/v2";
import { SessionStorageKeys } from "@app/const";
import { useOauthTokenExchange, useSelectOrganization } from "@app/hooks/api"; import { useOauthTokenExchange, useSelectOrganization } from "@app/hooks/api";
import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import { fetchMyPrivateKey } from "@app/hooks/api/users/queries"; import { fetchMyPrivateKey } from "@app/hooks/api/users/queries";
@@ -79,11 +81,24 @@ export const PasswordStep = ({
if (callbackPort) { if (callbackPort) {
console.log("organization id was present. new JWT token to be used in CLI:", newJwtToken); console.log("organization id was present. new JWT token to be used in CLI:", newJwtToken);
const instance = axios.create(); const instance = axios.create();
await instance.post(cliUrl, { const payload = {
privateKey, privateKey,
email, email,
JTWToken: newJwtToken JTWToken: newJwtToken
};
await instance.post(cliUrl, payload).catch(() => {
// if error happens to communicate we set the token with an expiry in sessino storage
// the cli-redirect page has logic to show this to user and ask them to paste it in terminal
sessionStorage.setItem(
SessionStorageKeys.CLI_TERMINAL_TOKEN,
JSON.stringify({
expiry: formatISO(addSeconds(new Date(), 30)),
data: window.btoa(JSON.stringify(payload))
})
);
}); });
router.push("/cli-redirect");
return;
} }
await navigateUserToOrg(router, organizationId); await navigateUserToOrg(router, organizationId);
@@ -165,26 +180,35 @@ export const PasswordStep = ({
); );
const instance = axios.create(); const instance = axios.create();
await instance.post(cliUrl, { const payload = {
...isCliLoginSuccessful.loginResponse, ...isCliLoginSuccessful.loginResponse,
JTWToken: newJwtToken JTWToken: newJwtToken
};
await instance.post(cliUrl, payload).catch(() => {
// if error happens to communicate we set the token with an expiry in sessino storage
// the cli-redirect page has logic to show this to user and ask them to paste it in terminal
sessionStorage.setItem(
SessionStorageKeys.CLI_TERMINAL_TOKEN,
JSON.stringify({
expiry: formatISO(addSeconds(new Date(), 30)),
data: window.btoa(JSON.stringify(payload))
})
);
}); });
router.push("/cli-redirect");
await navigateUserToOrg(router, organizationId); return;
} }
// case: no organization ID is present -- navigate to the select org page IF the user has any orgs // case: no organization ID is present -- navigate to the select org page IF the user has any orgs
// if the user has no orgs, navigate to the create org page // if the user has no orgs, navigate to the create org page
else { const userOrgs = await fetchOrganizations();
const userOrgs = await fetchOrganizations();
// case: user has orgs, so we navigate the user to select an org // case: user has orgs, so we navigate the user to select an org
if (userOrgs.length > 0) { if (userOrgs.length > 0) {
navigateToSelectOrganization(callbackPort); navigateToSelectOrganization(callbackPort);
} }
// case: no orgs found, so we navigate the user to create an org // case: no orgs found, so we navigate the user to create an org
else { else {
await navigateUserToOrg(router); await navigateUserToOrg(router);
}
} }
} }
} else { } else {