diff --git a/src/cli/cmd/login/mod.rs b/src/cli/cmd/login/mod.rs index c52838f2..273c6708 100644 --- a/src/cli/cmd/login/mod.rs +++ b/src/cli/cmd/login/mod.rs @@ -42,11 +42,15 @@ impl LoginSubcommand { println!(); let token = crate::cli::cmd::init::prompt::Prompt::maybe_string("Paste your token here:"); - let token = match token { + let (token, status) = match token { Some(token) => { - // FIXME: validate that the token is valid? - // or at least validate that it's a... jwt at all lol - token + // This serves as validating that provided token is actually a JWT, and is valid. + let status = crate::cli::cmd::status::get_status_from_auth_token( + self.api_addr.clone(), + &token, + ) + .await?; + (token, status) } None => { tracing::error!("Missing token."); @@ -111,7 +115,7 @@ impl LoginSubcommand { } if !self.skip_status { - crate::cli::cmd::status::get_status(self.api_addr.clone()).await?; + print!("{status}"); } Ok(()) diff --git a/src/cli/cmd/status/mod.rs b/src/cli/cmd/status/mod.rs index 7796f6bb..134afaec 100644 --- a/src/cli/cmd/status/mod.rs +++ b/src/cli/cmd/status/mod.rs @@ -1,6 +1,7 @@ use std::process::ExitCode; use clap::Parser; +use color_eyre::eyre::WrapErr; use super::CommandExecute; @@ -16,12 +17,22 @@ pub(crate) struct StatusSubcommand { } #[derive(Debug, serde::Deserialize)] -struct TokenStatus { +pub(crate) struct TokenStatus { gh_name: String, #[serde(deserialize_with = "i64_to_local_datetime")] expires_at: chrono::DateTime, } +impl std::fmt::Display for TokenStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Logged in: true")?; + writeln!(f, "GitHub user name: {}", self.gh_name)?; + writeln!(f, "Token expires at: {}", self.expires_at)?; + + Ok(()) + } +} + fn i64_to_local_datetime<'de, D>( deserializer: D, ) -> Result, D::Error> @@ -40,31 +51,50 @@ where #[async_trait::async_trait] impl CommandExecute for StatusSubcommand { async fn execute(self) -> color_eyre::Result { - get_status(self.api_addr).await?; + let status = get_status_from_auth_file(self.api_addr).await?; + print!("{status}"); Ok(ExitCode::SUCCESS) } } -pub(crate) async fn get_status(api_addr: url::Url) -> color_eyre::Result<()> { +pub(crate) async fn get_status_from_auth_file( + api_addr: url::Url, +) -> color_eyre::Result { let auth_token_path = crate::cli::cmd::login::auth_token_path()?; let token = tokio::fs::read_to_string(auth_token_path).await?; let token = token.trim(); + get_status_from_auth_token(api_addr, token).await +} + +pub(crate) async fn get_status_from_auth_token( + api_addr: url::Url, + token: &str, +) -> color_eyre::Result { let mut cli_status = api_addr; cli_status.set_path("/cli/status"); - let token_status: TokenStatus = reqwest::Client::new() + let res = reqwest::Client::new() .get(cli_status) .header("Authorization", &format!("Bearer {token}")) .send() - .await? - .json() - .await?; + .await + .wrap_err("Failed to send request")?; - println!("Logged in: true"); - println!("GitHub user name: {}", token_status.gh_name); - println!("Token expires at: {}", token_status.expires_at); + if res.status() == 401 { + return Err(color_eyre::eyre::eyre!( + "The provided token was invalid. Please try again, or contact support@flakehub.com if the problem persists." + )); + } + + let res = res + .error_for_status() + .wrap_err("Request was unsuccessful")?; + let token_status: TokenStatus = res + .json() + .await + .wrap_err("Failed to get TokenStatus from response (wasn't JSON, or was invalid JSON?)")?; - Ok(()) + Ok(token_status) }