zero-cli 1.0.1

A command line tool for Zero Secrets Manager
pub mod graphql;
use crate::auth::login::graphql::cli_access_tokens::{cli_access_tokens, CliAccessTokens};
use crate::auth::login::graphql::user_by_pk::{user_by_pk, UserByPk};
use crate::common::{
    authorization_headers::authorization_headers, env_var::env_var,
    execute_graphql_request::execute_graphql_request, fetch_user_id::fetch_user_id,
    keyring::keyring, print_formatted_error::print_formatted_error,
};
use clap::Args;
use dialoguer::{theme::ColorfulTheme, Input};
use graphql_client::GraphQLQuery;
use reqwest::{header, Client};
use std::io;
use termimad::crossterm::style::Stylize;

#[derive(Args, Debug)]
pub struct AuthLoginArgs {
    #[clap(
        short,
        long,
        help = "If true - returns the access token to the console, if false or not specified - saves the access token to the keychain"
    )]
    console_output: Option<bool>,
}

pub fn login(args: &AuthLoginArgs) {
    let webapp_cli_auth_url = env_var("WEBAPP_CLI_AUTH_URL");
    let client = Client::new();

    let is_console_output = match &args.console_output {
        Some(token) => token.clone(),
        _ => false,
    };

    println!(
        "Press `Enter` to open tryzero.com in your browser or type `q` to quit.\n{}",
        &webapp_cli_auth_url
    );

    // If the user pressed Enter, open the authorization page in the browser
    // and ask him to enter the code from the browser
    loop {
        let mut input = String::new();

        io::stdin()
            .read_line(&mut input)
            .expect("Failed to read line");

        input = input.trim().to_string();

        match input.as_str() {
            "" => {
                match open::that(&webapp_cli_auth_url) {
                    Ok(_) => (),
                    Err(_) => print_formatted_error("Failed to open browser"),
                };

                let code = match Input::with_theme(&ColorfulTheme::default())
                    .with_prompt("Type the code shown in the browser:")
                    .validate_with(|input: &String| -> Result<(), &str> {
                        if input.trim().chars().count() == 32 {
                            Ok(())
                        } else {
                            Err("The code must be 32 characters long.")
                        }
                    })
                    .interact()
                {
                    Ok(value) => value.trim().to_string(),

                    Err(_) => {
                        print_formatted_error("Failed to read the code.");
                        std::process::exit(1);
                    }
                };

                // Check the code and get the access token
                let access_token = execute_graphql_request::<cli_access_tokens::Variables, cli_access_tokens::ResponseData>(
                    header::HeaderMap::new(),
                    CliAccessTokens::build_query,
                    &client,
                    "Failed to retrieve access token, please check if the code is correct and try again.",
                    cli_access_tokens::Variables::new(code),
                ).cli_access_tokens.access_token;

                let user_info_error_message = "Failed to retrieve user info";

                // Send a request for user info
                let user_info_response =
                    execute_graphql_request::<user_by_pk::Variables, user_by_pk::ResponseData>(
                        authorization_headers(&access_token),
                        UserByPk::build_query,
                        &client,
                        user_info_error_message,
                        user_by_pk::Variables {
                            id: fetch_user_id(&access_token),
                        },
                    )
                    .user_by_pk;

                let user_info = match user_info_response {
                    Some(user_info) => user_info,

                    None => {
                        print_formatted_error(user_info_error_message);
                        std::process::exit(1);
                    }
                };

                match is_console_output {
                    true => {
                        println!("{} You are authorized as {}", "".green(), user_info.name);

                        println!(
                            "{}",
                            "Be careful! Do not share your access token with anyone.".yellow()
                        );

                        println!("{} {}", "Your access token is:".bold(), access_token);
                        std::process::exit(0);
                    }

                    false => {
                        keyring::set("access_token", &access_token);
                        println!("{} You are authorized as {}", "".green(), user_info.name);
                        std::process::exit(0);
                    }
                }
            }

            "q" | "Q" => {
                std::process::exit(0);
            }

            _ => {
                println!("You've entered: {}. Press only ENTER to continue.", input);
            }
        }
    }
}