From c88da7fddb024cd589ea6e3b91036a9b30d50a18 Mon Sep 17 00:00:00 2001 From: antifallobst Date: Sun, 31 Mar 2024 17:55:53 +0200 Subject: [PATCH] feat(client >> session): implemented session authentiaction with credentials --- client/src/error.rs | 6 +++ client/src/lib.rs | 16 +++++- client/src/session/data.rs | 16 ++++++ client/src/session/mod.rs | 104 +++++++++++++++++++++++++------------ 4 files changed, 107 insertions(+), 35 deletions(-) diff --git a/client/src/error.rs b/client/src/error.rs index 41a8688..b976c79 100644 --- a/client/src/error.rs +++ b/client/src/error.rs @@ -1,5 +1,8 @@ #[derive(Debug, thiserror::Error, Clone)] pub enum Error { + #[error("Failed to authenticate with the given password")] + WrongPassword, + #[error("Failed to handle a server response: {0}")] BadResponse(String), @@ -14,4 +17,7 @@ pub enum Error { #[error("{0}")] Unknown(String), + + #[error("The user was not found.")] + UserNotFound, } diff --git a/client/src/lib.rs b/client/src/lib.rs index 3711d10..a0d7160 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -6,6 +6,8 @@ pub use session::Session; #[cfg(test)] mod tests { use super::*; + use std::str::FromStr; + use uuid::Uuid; #[tokio::test] async fn authorized_session() { @@ -15,7 +17,19 @@ mod tests { .expect("The environment variable ICRC_TEST_AUTH needs to be set!"); let session = Session::new(&host, Some(token)).await.unwrap(); + } - // assert_eq!(result, 4); + #[tokio::test] + async fn authenticate() { + let host = std::env::var("ICRC_TEST_HOST") + .expect("The environment variable ICRC_TEST_HOST needs to be set!"); + let userid = std::env::var("ICRC_TEST_USER") + .expect("The environment variable ICRC_TEST_USER needs to be set!"); + + let mut session = Session::new(&host, None).await.unwrap(); + session + .auth_with_credentials(Uuid::from_str(&userid).unwrap(), "test".to_string()) + .await + .unwrap() } } diff --git a/client/src/session/data.rs b/client/src/session/data.rs index aaf45e2..ca7a3c8 100644 --- a/client/src/session/data.rs +++ b/client/src/session/data.rs @@ -12,6 +12,22 @@ pub mod api { pub mod account { use super::*; + pub mod auth { + use super::*; + use serde::Serialize; + + #[derive(Debug, Serialize)] + pub struct Request { + pub userid: String, + pub password: String, + } + + #[derive(Debug, Deserialize)] + pub struct Response { + pub token: String, + } + } + pub mod info { use super::*; diff --git a/client/src/session/mod.rs b/client/src/session/mod.rs index ca4e73b..5adf1b0 100644 --- a/client/src/session/mod.rs +++ b/client/src/session/mod.rs @@ -1,12 +1,14 @@ pub(self) mod data; use crate::error::Error; +use reqwest::Response; +use serde::de::DeserializeOwned; use std::str::FromStr; use uuid::Uuid; struct AuthorizedSession { - token: String, - userid: Uuid, + pub(self) token: String, + pub(self) userid: Uuid, } /// A Session is a connection to an ICRC Server. @@ -15,10 +17,44 @@ struct AuthorizedSession { /// but an authorized session uses Bearer Authentication to get access to resources, /// which are not publicly accessible. pub struct Session { - host: String, - client: reqwest::Client, + pub(self) host: String, + pub(self) client: reqwest::Client, - auth: Option, + pub(self) auth: Option, +} + +pub(self) async fn parse_response( + response: Result, +) -> Result { + match response { + Ok(resp) => { + if resp.status().is_success() { + resp.json::() + .await + .map_err(|e| Error::BadResponse(e.to_string())) + } else { + return Err( + match resp + .json::() + .await + .map_err(|e| Error::BadResponse(e.to_string()))? + .error + { + data::api::error::Error::InvalidToken => Error::InvalidToken, + data::api::error::Error::TokenExpired => Error::TokenExpired, + e => Error::BadResponse(format!("Unknown Error/{e} in this context")), + }, + ); + } + } + Err(e) => { + if e.is_connect() || e.is_timeout() { + Err(Error::ConnectionError) + } else { + Err(Error::Unknown(e.to_string())) + } + } + } } impl Session { @@ -48,36 +84,10 @@ impl Session { let request = self .client .get(format!("{host}/account/info", host = self.host)) - .bearer_auth(&token); + .bearer_auth(&token) + .send(); - let response = match request.send().await { - Ok(resp) => { - if resp.status().is_success() { - resp.json::() - .await - .map_err(|e| Error::BadResponse(e.to_string()))? - } else { - return Err( - match resp - .json::() - .await - .map_err(|e| Error::BadResponse(e.to_string()))? - .error - { - data::api::error::Error::InvalidToken => Error::InvalidToken, - data::api::error::Error::TokenExpired => Error::TokenExpired, - e => Error::BadResponse(format!("Unknown Error/{e} in this context")), - }, - ); - } - } - Err(e) => { - if e.is_connect() || e.is_timeout() { - return Err(Error::ConnectionError); - } - return Err(Error::Unknown(e.to_string())); - } - }; + let response = parse_response::(request.await).await?; self.auth = Some(AuthorizedSession { token, @@ -87,4 +97,30 @@ impl Session { Ok(()) } + + /// Tries to authorize the session. If the credentials are not accepted by the server, this + /// leads to a [crate::error::Error::AuthenticationFailure] or [crate::error::Error::UserNotFound] error. + pub async fn auth_with_credentials( + &mut self, + userid: Uuid, + password: String, + ) -> Result<(), Error> { + let request = self + .client + .post(format!("{host}/account/auth", host = self.host)) + .json(&data::api::account::auth::Request { + userid: userid.to_string(), + password, + }) + .send(); + + let response = parse_response::(request.await).await?; + + self.auth = Some(AuthorizedSession { + token: response.token, + userid, + }); + + Ok(()) + } }