feat(client >> storage): storing auth tokens in the persistent session storage system
This commit is contained in:
parent
08cf73a613
commit
3b240d6ca9
|
@ -7,6 +7,7 @@ pub use session::Session;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::storage::standard::StandardStorage;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -17,7 +18,23 @@ mod tests {
|
||||||
let token = std::env::var("ICRC_TEST_AUTH")
|
let token = std::env::var("ICRC_TEST_AUTH")
|
||||||
.expect("The environment variable ICRC_TEST_AUTH needs to be set!");
|
.expect("The environment variable ICRC_TEST_AUTH needs to be set!");
|
||||||
|
|
||||||
let session = Session::new(&host, Some(token)).await.unwrap();
|
let _session = Session::new(
|
||||||
|
&host,
|
||||||
|
Some(token),
|
||||||
|
StandardStorage::new("sqlite:test.db").await.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let new_session = Session::new(
|
||||||
|
&host,
|
||||||
|
None,
|
||||||
|
StandardStorage::new("sqlite:test.db").await.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(new_session.is_authorized())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -27,7 +44,13 @@ mod tests {
|
||||||
let userid = std::env::var("ICRC_TEST_USER")
|
let userid = std::env::var("ICRC_TEST_USER")
|
||||||
.expect("The environment variable ICRC_TEST_USER needs to be set!");
|
.expect("The environment variable ICRC_TEST_USER needs to be set!");
|
||||||
|
|
||||||
let mut session = Session::new(&host, None).await.unwrap();
|
let mut session = Session::new(
|
||||||
|
&host,
|
||||||
|
None,
|
||||||
|
StandardStorage::new("sqlite::memory:").await.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
session
|
session
|
||||||
.auth_with_credentials(Uuid::from_str(&userid).unwrap(), "test")
|
.auth_with_credentials(Uuid::from_str(&userid).unwrap(), "test")
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
pub(self) mod data;
|
pub(self) mod data;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::{
|
||||||
|
error::Error,
|
||||||
|
storage::{self, Storage, StorageError},
|
||||||
|
};
|
||||||
use reqwest::Response;
|
use reqwest::Response;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -16,52 +19,28 @@ struct AuthorizedSession {
|
||||||
/// An unauthorized session is not linked to any account on the server,
|
/// An unauthorized session is not linked to any account on the server,
|
||||||
/// but an authorized session uses Bearer Authentication to get access to resources,
|
/// but an authorized session uses Bearer Authentication to get access to resources,
|
||||||
/// which are not publicly accessible.
|
/// which are not publicly accessible.
|
||||||
pub struct Session {
|
pub struct Session<S>
|
||||||
|
where
|
||||||
|
S: Storage,
|
||||||
|
{
|
||||||
pub(self) host: String,
|
pub(self) host: String,
|
||||||
pub(self) client: reqwest::Client,
|
pub(self) client: reqwest::Client,
|
||||||
|
pub(self) storage: S,
|
||||||
|
|
||||||
pub(self) auth: Option<AuthorizedSession>,
|
pub(self) auth: Option<AuthorizedSession>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(self) async fn parse_response<T: DeserializeOwned>(
|
impl<S: Storage> Session<S> {
|
||||||
response: Result<Response, reqwest::Error>,
|
/// Returns whether the session is authorized or not.
|
||||||
) -> Result<T, Error> {
|
pub fn is_authorized(&self) -> bool {
|
||||||
match response {
|
self.auth.is_some()
|
||||||
Ok(resp) => {
|
|
||||||
if resp.status().is_success() {
|
|
||||||
resp.json::<T>()
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::BadResponse(e.to_string()))
|
|
||||||
} else {
|
|
||||||
return Err(
|
|
||||||
match resp
|
|
||||||
.json::<data::api::error::Body>()
|
|
||||||
.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 {
|
/// Creates a new [Session] with `host` as the ICRC Server host. Setting `auth` to [None] lets
|
||||||
/// Creates a new [Session] with `host` as the ICRC Server host.
|
/// this function try to fetch an auth token from it's [Storage]. If this is not successful the
|
||||||
/// Giving `auth` the value [None] sets the session in an unauthorized state,
|
/// session is in an unauthorized state. Setting `auth` to [`Some(token)`] calls
|
||||||
/// whilst [Some]`(token)` calls [Session::auth_with_token].
|
/// [Session::auth_with_token] (which overrides any saved auth token in the storage).
|
||||||
pub async fn new(mut host: &str, auth: Option<String>) -> Result<Self, Error> {
|
pub async fn new(mut host: &str, auth: Option<String>, storage: S) -> Result<Self, Error> {
|
||||||
if let Some(stripped) = host.strip_suffix("/") {
|
if let Some(stripped) = host.strip_suffix("/") {
|
||||||
host = stripped;
|
host = stripped;
|
||||||
}
|
}
|
||||||
|
@ -69,17 +48,34 @@ impl Session {
|
||||||
let mut session = Self {
|
let mut session = Self {
|
||||||
host: host.to_string(),
|
host: host.to_string(),
|
||||||
client: reqwest::Client::new(),
|
client: reqwest::Client::new(),
|
||||||
|
storage,
|
||||||
auth: None,
|
auth: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(token) = auth {
|
if let Some(token) = auth {
|
||||||
session.auth_with_token(token).await?;
|
session.auth_with_token(token).await?;
|
||||||
|
} else {
|
||||||
|
match session
|
||||||
|
.storage
|
||||||
|
.pull(storage::keys::KEY_ACCOUNT_AUTH_TOKEN)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(data) => {
|
||||||
|
let token = String::from_utf8(data.to_owned())
|
||||||
|
.map_err(|e| Error::Unknown(e.to_string()))?;
|
||||||
|
session.auth_with_token(token).await?
|
||||||
|
}
|
||||||
|
Err(StorageError::IdNotFound(_)) => (),
|
||||||
|
Err(e) => return Err(Error::Unknown(e.to_string())),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(session)
|
Ok(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to authorize the session. If `token` is not accepted by the server, this leads to an
|
/// Tries to authorize the session. If `token` is not accepted by the server, this leads to an
|
||||||
/// [crate::error::Error::InvalidToken] or [crate::error::Error::TokenExpired] error.
|
/// [crate::error::Error::InvalidToken] or [crate::error::Error::TokenExpired] error. If the
|
||||||
|
/// session is successfully authorized, the auth token is saved in the sessions [Storage].
|
||||||
pub async fn auth_with_token(&mut self, token: String) -> Result<(), Error> {
|
pub async fn auth_with_token(&mut self, token: String) -> Result<(), Error> {
|
||||||
let request = self
|
let request = self
|
||||||
.client
|
.client
|
||||||
|
@ -89,6 +85,18 @@ impl Session {
|
||||||
|
|
||||||
let response = parse_response::<data::api::account::info::Response>(request.await).await?;
|
let response = parse_response::<data::api::account::info::Response>(request.await).await?;
|
||||||
|
|
||||||
|
match self
|
||||||
|
.storage
|
||||||
|
.push(
|
||||||
|
storage::keys::KEY_ACCOUNT_AUTH_TOKEN,
|
||||||
|
token.to_owned().into_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) | Err(StorageError::IdCollision(_)) => (),
|
||||||
|
Err(e) => return Err(Error::Unknown(e.to_string())),
|
||||||
|
}
|
||||||
|
|
||||||
self.auth = Some(AuthorizedSession {
|
self.auth = Some(AuthorizedSession {
|
||||||
token,
|
token,
|
||||||
userid: Uuid::from_str(&response.userid)
|
userid: Uuid::from_str(&response.userid)
|
||||||
|
@ -149,3 +157,37 @@ impl Session {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(self) async fn parse_response<T: DeserializeOwned>(
|
||||||
|
response: Result<Response, reqwest::Error>,
|
||||||
|
) -> Result<T, Error> {
|
||||||
|
match response {
|
||||||
|
Ok(resp) => {
|
||||||
|
if resp.status().is_success() {
|
||||||
|
resp.json::<T>()
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::BadResponse(e.to_string()))
|
||||||
|
} else {
|
||||||
|
return Err(
|
||||||
|
match resp
|
||||||
|
.json::<data::api::error::Body>()
|
||||||
|
.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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
pub const KEY_ACCOUNT_AUTH_TOKEN: &str = "account.auth.token";
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub(crate) mod keys;
|
||||||
pub mod standard;
|
pub mod standard;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Clone)]
|
#[derive(Debug, thiserror::Error, Clone)]
|
||||||
|
|
Loading…
Reference in New Issue