diff options
Diffstat (limited to 'crates/secd/src/util')
| -rw-r--r-- | crates/secd/src/util/from.rs | 66 | ||||
| -rw-r--r-- | crates/secd/src/util/mod.rs | 189 |
2 files changed, 99 insertions, 156 deletions
diff --git a/crates/secd/src/util/from.rs b/crates/secd/src/util/from.rs new file mode 100644 index 0000000..bab8a25 --- /dev/null +++ b/crates/secd/src/util/from.rs @@ -0,0 +1,66 @@ +use std::str::FromStr; + +use crate::AuthStore; + +impl From<Option<String>> for AuthStore { + fn from(s: Option<String>) -> Self { + let conn = s.clone().unwrap_or("sqlite::memory:".into()); + match conn.split(":").next() { + Some("postgresql") | Some("postgres") => AuthStore::Postgres { conn }, + Some("sqlite") => AuthStore::Sqlite { conn }, + _ => panic!( + "AuthStore: Invalid database connection string provided. Found: {:?}", + s + ), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn auth_store_from_string() { + let in_conn = None; + let a = AuthStore::from(in_conn.clone()); + match AuthStore::from(in_conn.clone()) { + AuthStore::Sqlite { conn } => assert_eq!(conn, "sqlite::memory:"), + r @ _ => assert!( + false, + "should have parsed None as in-memory sqlite. Found: {:?}", + r + ), + } + + let postgresql_conn = Some("postgresql://testuser:p4ssw0rd@1.2.3.4:5432/test-db".into()); + match AuthStore::from(postgresql_conn.clone()) { + AuthStore::Postgres { conn } => assert_eq!(conn, postgresql_conn.unwrap()), + r @ _ => assert!(false, "should have parsed as postgres. Found: {:?}", r), + } + + let postgres_conn = Some("postgres://testuser:p4ssw0rd@1.2.3.4:5432/test-db".into()); + match AuthStore::from(postgres_conn.clone()) { + AuthStore::Postgres { conn } => assert_eq!(conn, postgres_conn.unwrap()), + r @ _ => assert!(false, "should have parsed as postgres. Found: {:?}", r), + } + + let sqlite_conn = Some("sqlite:///path/to/db.sql".into()); + let a = AuthStore::from(sqlite_conn.clone()); + match AuthStore::from(sqlite_conn.clone()) { + AuthStore::Sqlite { conn } => assert_eq!(conn, sqlite_conn.unwrap()), + r @ _ => assert!(false, "should have parsed as sqlite. Found: {:?}", r), + } + + let sqlite_mem_conn = Some("sqlite:memory:".into()); + let a = AuthStore::from(sqlite_mem_conn.clone()); + match AuthStore::from(sqlite_mem_conn.clone()) { + AuthStore::Sqlite { conn } => assert_eq!(conn, sqlite_mem_conn.unwrap()), + r @ _ => assert!( + false, + "should have parsed as in-memoy sqlite. Found: {:?}", + r + ), + } + } +} diff --git a/crates/secd/src/util/mod.rs b/crates/secd/src/util/mod.rs index bb177cb..6677c2f 100644 --- a/crates/secd/src/util/mod.rs +++ b/crates/secd/src/util/mod.rs @@ -1,38 +1,12 @@ -use std::str::FromStr; +pub(crate) mod from; -use anyhow::{bail, Context}; -use log::error; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; -use reqwest::header; -use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use time::OffsetDateTime; use url::Url; -use crate::{ - OauthProvider, OauthProviderName, OauthValidation, SecdError, ValidationRequestId, - INTERNAL_ERR_MSG, -}; - -pub(crate) fn log_err(e: Box<dyn std::error::Error>, new_e: SecdError) -> SecdError { - error!("{:?}", e); - new_e -} -pub(crate) fn to_secd_err(e: anyhow::Error, new_e: SecdError) -> SecdError { - error!("{:?}", e); - new_e -} - -pub(crate) fn log_err_sqlx(e: sqlx::Error) -> sqlx::Error { - error!("{:?}", e); - e -} -pub(crate) fn generate_random_url_safe(n: usize) -> String { - thread_rng() - .sample_iter(&Alphanumeric) - .take(n) - .map(char::from) - .collect() -} +use crate::{AddressType, IdentityId, SecdError, Session, SESSION_DURATION, SESSION_SIZE_BYTES}; pub(crate) fn remove_trailing_slash(url: &mut Url) -> String { let mut u = url.to_string(); @@ -44,134 +18,37 @@ pub(crate) fn remove_trailing_slash(url: &mut Url) -> String { u } -pub(crate) fn build_oauth_auth_url( - p: &OauthProvider, - validation_id: ValidationRequestId, -) -> Result<Url, SecdError> { - let redirect_url = remove_trailing_slash(&mut p.redirect_url.clone()); - - Ok(Url::from_str(&format!( - "{}?client_id={}&response_type={}&redirect_uri={}&scope={}&state={}", - p.base_url, - p.client_id, - p.response.to_string().to_lowercase(), - redirect_url, - p.default_scope, - validation_id.to_string() - )) - .map_err(|_| SecdError::InternalError(INTERNAL_ERR_MSG.into()))?) -} - -pub(crate) async fn get_oauth_identity_data( - validation: &OauthValidation, -) -> anyhow::Result<OauthAccessIdentity> { - let provider = validation.oauth_provider.name; - let token = validation - .access_token - .clone() - .ok_or(SecdError::InternalError( - "no access token provided with which to build oauth data url".into(), - ))?; - - let url = Url::from_str(&format!( - "{}{}", - match provider { - OauthProviderName::Google => - "https://www.googleapis.com/oauth2/v2/userinfo?access_token=", - _ => unimplemented!(), - }, - token - ))?; - - let resp = reqwest::get(url).await?.json::<serde_json::Value>().await?; - let identity = match provider { - OauthProviderName::Google => OauthAccessIdentity { - email: resp - .get("email") - .and_then(|v| v.as_str().map(|s| s.to_string())), - email_is_verified: resp.get("verified_email").and_then(|v| v.as_bool()), - picture_url: resp - .get("picture") - .and_then(|v| Url::from_str(&v.to_string()).ok()), - }, - _ => unimplemented!(), - }; - - Ok(identity) -} - -#[derive(Debug, Serialize)] -pub(crate) struct OauthAccessTokenGoogleRequest { - grant_type: String, - code: String, - client_id: String, - client_secret: String, - redirect_uri: String, -} - -#[derive(Debug, Deserialize)] -pub(crate) struct OauthAccessTokenGoogleResponse { - access_token: String, - expires_in: i32, - token_type: String, - scope: String, - id_token: String, +pub(crate) fn hash(i: &[u8]) -> Vec<u8> { + let mut hasher = Sha256::new(); + hasher.update(i); + hasher.finalize().to_vec() } -#[derive(Debug)] -pub(crate) struct OauthAccessIdentity { - pub(crate) email: Option<String>, - pub(crate) email_is_verified: Option<bool>, - pub(crate) picture_url: Option<Url>, -} - -type AccessTokenRequestData = String; - -pub(crate) async fn get_oauth_access_token( - validation: &OauthValidation, - secret_code: &String, -) -> anyhow::Result<String> { - let provider = validation.oauth_provider.name; - - let url = Url::from_str(match provider { - OauthProviderName::Google => "https://accounts.google.com/o/oauth2/token", - _ => unimplemented!(), - })?; - - let request_data = serde_json::to_string(&match provider { - OauthProviderName::Google => OauthAccessTokenGoogleRequest { - grant_type: "authorization_code".to_string(), - code: secret_code.to_string(), - client_id: validation.oauth_provider.client_id.clone(), - client_secret: validation.oauth_provider.client_secret.clone(), - redirect_uri: remove_trailing_slash( - &mut validation.oauth_provider.redirect_url.clone(), - ), - }, - _ => unimplemented!(), - })?; - - let r = reqwest::Client::new() - .post(url) - .body(request_data) - .header(header::CONTENT_TYPE, "application/json") - .send() - .await - .context(format!( - "Failed to successfully POST a new access token for: {}", - provider - ))?; - - let access_token = match provider { - OauthProviderName::Google => { - let resp: OauthAccessTokenGoogleResponse = r.json().await.context(format!( - "Failed to parse access token response for: {}", - provider - ))?; - resp.access_token +impl AddressType { + pub fn get_value(&self) -> Option<String> { + match &self { + AddressType::Email { email_address } => { + email_address.as_ref().map(|a| a.to_string().clone()) + } + AddressType::Sms { phone_number } => phone_number.as_ref().cloned(), } - _ => unimplemented!(), - }; + } +} - Ok(access_token) +impl Session { + pub(crate) fn new(identity_id: IdentityId) -> Result<Self, SecdError> { + let token = (0..SESSION_SIZE_BYTES) + .map(|_| rand::random::<u8>()) + .collect::<Vec<u8>>(); + let now = OffsetDateTime::now_utc(); + Ok(Session { + identity_id, + token, + created_at: now, + expired_at: now + .checked_add(time::Duration::new(SESSION_DURATION, 0)) + .ok_or(SecdError::Todo)?, + revoked_at: None, + }) + } } |
