Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for 2FA codes through environment variables #41

Merged

Conversation

lesteenman
Copy link
Contributor

This PR adds support for a 3rd authentication environment variable, QBEE_2FA_CODE. If provided, it will be used as a Google authentication code for the 2FA process, rather than requiring interactive user input.

This helps use the qbee-cli in scripts (in my case, shorthands to connect to a device by its hostname instead of node_id) without having to make your account less secure by removing 2FA.

@jonhenrik13
Copy link
Contributor

Thanks for your contribution. This looks good, but I'd like to suggest that we put the resolving of 2FA details in its own function to keep the function size low and for ease of extending the 2FA details resolution.

Something like this:


// Login2FA returns a new authenticated API Client.
func (cli *Client) Login2FA(ctx context.Context, challenge string) (string, error) {
	resolved2FADetails, err := resolve2FADetails()

	if err != nil {
		return "", err
	}

	requestPrepare := &Login2FARequest{
		Challenge: challenge,
		Provider:  resolved2FADetails.Provider,
	}

	responsePrepare := new(Login2FAResponse)
	if err := cli.Call(ctx, http.MethodPost, login2FAChallengeGetPath, requestPrepare, &responsePrepare); err != nil {
		return "", err
	}

	if resolved2FADetails.Code == "" {
		fmt.Printf("Enter 2FA code: ")

		scanner := bufio.NewScanner(os.Stdin)
		scanner.Scan()
		err := scanner.Err()
		if err != nil {
			log.Fatal(err)
		}

		resolved2FADetails.Code = scanner.Text()
	}

	requestVerify := &Login2FARequest{
		Challenge: responsePrepare.Challenge,
		Code:      resolved2FADetails.Code,
	}

	responseVerify := new(Login2FAResponse)

	if err := cli.Call(ctx, http.MethodPost, login2FAChallengeVerifyPath, requestVerify, &responseVerify); err != nil {
		return "", err
	}

	return responseVerify.Token, nil
}

// Login2FADetails holds the 2FA provider and code
type Login2FADetails struct {
	Provider string
	Code     string
}

// resolve2FADetails resolves the 2FA provider and code
func resolve2FADetails() (*Login2FADetails, error) {

	if os.Getenv("QBEE_2FA_CODE") != "" {
		fmt.Printf("Using 2FA code from environment variable QBEE_2FA_CODE as a google provider\n")

		return &Login2FADetails{
			Provider: "google",
			Code:     os.Getenv("QBEE_2FA_CODE"),
		}, nil
	}

	fmt.Printf("Select 2FA provider:\n")

	for i, provider := range valid2FAProviders {
		fmt.Printf("%d) %s\n", i+1, provider)
	}

	fmt.Printf("Choice: ")

	scanner := bufio.NewScanner(os.Stdin)
	scanner.Scan()
	err := scanner.Err()
	if err != nil {
		log.Fatal(err)
	}

	providerIndex := scanner.Text()

	index, err := strconv.Atoi(providerIndex)
	if err != nil {
		return nil, err
	}

	if index < 1 || index > len(valid2FAProviders) {
		return nil, fmt.Errorf("invalid provider")
	}

	return &Login2FADetails{
		Provider: valid2FAProviders[index-1],
		Code:     "",
	}, nil
}

Would that work for you?

Copy link
Contributor

@jonhenrik13 jonhenrik13 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see comments in the PR.

@lesteenman
Copy link
Contributor Author

@jonhenrik13 I'd already considered refactoring that method due to the complexity, but had a hard time seeing how it should be split out while remaining consistent. In the end, I've refactored it a bit more. Going by my own tests, it seems to be equivalent to the old behaviour. Please let me know what you think!

@lesteenman lesteenman requested a review from jonhenrik13 August 29, 2024 14:20
@jonhenrik13 jonhenrik13 merged commit 22e6e07 into qbee-io:main Sep 2, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants