From aa8c20d501b58001a5e1b24964c62363e2112ff8 Mon Sep 17 00:00:00 2001 From: benj Date: Fri, 25 Nov 2022 16:42:16 -0800 Subject: some shell is coming together and a rough API --- src/client/mod.rs | 81 +++++++++++++++++++++++++ src/client/sqldb.rs | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 src/client/mod.rs create mode 100644 src/client/sqldb.rs (limited to 'src/client') diff --git a/src/client/mod.rs b/src/client/mod.rs new file mode 100644 index 0000000..bb32e2c --- /dev/null +++ b/src/client/mod.rs @@ -0,0 +1,81 @@ +pub mod sqldb; + +use thiserror::Error; +use uuid::Uuid; + +use super::Identity; + +#[derive(Error, Debug)] +pub enum StoreError { + #[error("sqlx client error")] + SqlxError(#[from] sqlx::Error), + #[error( + "More than one oauth provider identified, but no client_id was provided for disambiguation" + )] + TooManyOauthProviders, + #[error("Oath provider not registered. First register the Oauth provider before executing")] + OauthProviderNotRegistered, + #[error("An unknown error occurred")] + Unknown, +} + +#[async_trait::async_trait] +pub trait Store { + // async fn read_oauth_authorization_location( + // &self, + // provider: OauthProvider, + // client_id: Option, + // ) -> Result; + + // async fn write_oauth_authorization_request( + // &self, + // identity_id: Uuid, + // provider: OauthProvider, + // raw: String, + // state: String, + // ) -> Result<(), StoreError>; + + // async fn write_oauth_provider( + // &self, + // provider: OauthProvider, + // consent_uri: OauthConsentUri, + // client_id: OauthClientId, + // client_secret: OauthClientSecretEncrypted, + // redirect_uri: String, + // ) -> Result<(), StoreError>; + + // fn read_email_challenge(&self) -> Result; + // fn write_email_challenge(&self) -> Result; + + async fn write_email(&self, id: Uuid, email_address: &str) -> Result<(), StoreError>; + async fn write_email_validation_request( + &self, + id: Uuid, + email_address: &str, + ) -> Result; + + async fn find_identity( + &self, + id: Option<&Uuid>, + email: Option<&str>, + ) -> Result, StoreError>; + async fn write_identity(&self, i: &Identity) -> Result<(), StoreError>; + async fn read_identity(&self, id: &Uuid) -> Result; + + // fn read_sms_challenge(&self) -> Result; + // fn write_sms_challenge(&self) -> Result; +} + +// #[derive(sqlx::FromRow, Debug)] +// struct Identity { +// #[sqlx(rename = "identity_public_id")] +// id: Uuid, +// } + +// #[derive(sqlx::FromRow, Debug)] +// struct OauthProviderRecord { +// consent_uri: String, +// client_id: OauthClientId, +// client_secret_encrypted: OauthClientSecretEncrypted, +// redirect_uri: String, +// } diff --git a/src/client/sqldb.rs b/src/client/sqldb.rs new file mode 100644 index 0000000..6ad0cc1 --- /dev/null +++ b/src/client/sqldb.rs @@ -0,0 +1,172 @@ +use log::{debug, error}; +use uuid::Uuid; + +use crate::{util, Identity}; + +use super::{Store, StoreError}; + +pub struct SqliteClient { + pool: sqlx::Pool, +} + +impl SqliteClient { + pub async fn new(pool: sqlx::Pool) -> Self { + sqlx::migrate!("store/sqlite/migrations") + .run(&pool) + .await + .expect( + "Failed to execute migrations. This appears to be a secd issue. File a bug at https://www.github.com/secd-lib" + ); + + sqlx::query("pragma foreign_keys = on") + .execute(&pool) + .await + .expect( + "Failed to initialize FK pragma. File a bug at https://www.github.com/secd-lib", + ); + + SqliteClient { pool } + } +} + +impl SqliteClient { + async fn read_identity_raw_id(&self, id: &Uuid) -> Result { + Ok(sqlx::query_as::<_, (i64,)>( + " +select identity_id from identity where identity_public_id = ?", + ) + .bind(id) + .fetch_one(&self.pool) + .await + .map_err(util::log_err)? + .0) + } + + async fn read_email_raw_id(&self, address: &str) -> Result { + Ok(sqlx::query_as::<_, (i64,)>( + " +select email_id from email where address = ?", + ) + .bind(address) + .fetch_one(&self.pool) + .await + .map_err(util::log_err)? + .0) + } +} + +#[async_trait::async_trait] +impl Store for SqliteClient { + async fn write_email(&self, identity_id: Uuid, email_address: &str) -> Result<(), StoreError> { + let mut tx = self.pool.begin().await?; + + let identity_id = self.read_identity_raw_id(&identity_id).await?; + + let email_id: (i64,) = sqlx::query_as( + " +insert into email (address) values (?) returning email_id", + ) + .bind(email_address) + .fetch_one(&mut tx) + .await + .map_err(util::log_err)?; + + debug!("identity: {}, email: {}", identity_id, email_id.0); + + sqlx::query( + " +insert into identity_email (identity_id, email_id) values (?,?);", + ) + .bind(identity_id) + .bind(email_id.0) + .execute(&mut tx) + .await + .map_err(util::log_err)?; + + tx.commit().await?; + + Ok(()) + } + + async fn write_email_validation_request( + &self, + identity_id: Uuid, + email_address: &str, + ) -> Result { + let identity_id = self.read_identity_raw_id(&identity_id).await?; + let email_id = self.read_email_raw_id(email_address).await?; + + let request_id = Uuid::new_v4(); + sqlx::query(" +insert into email_validation_request (email_validation_request_public_id, identity_email_id, is_validated) +values (?, (select identity_email_id from identity_email where identity_id = ? and email_id =?), ?)", + ) + .bind(request_id) + .bind(identity_id) + .bind(email_id) + .bind(false) + .execute(&self.pool) + .await + .map_err(util::log_err)?; + + Ok(request_id) + } + + async fn write_identity(&self, i: &Identity) -> Result<(), StoreError> { + sqlx::query( + " +insert into identity (identity_public_id, data, created_at) values (?, ?, ?)", + ) + .bind(i.id) + .bind(i.data.clone()) + .bind(i.created_at) + .execute(&self.pool) + .await + .map_err(|e| { + error!("{:?}", e); + e + })?; + + Ok(()) + } + + async fn read_identity(&self, id: &Uuid) -> Result { + Ok(sqlx::query_as::<_, Identity>( + " +select identity_public_id, data, created_at from identity where identity_public_id = ?", + ) + .bind(id) + .fetch_one(&self.pool) + .await + .map_err(util::log_err)?) + } + + async fn find_identity( + &self, + id: Option<&Uuid>, + email: Option<&str>, + ) -> Result, StoreError> { + Ok( + match sqlx::query_as::<_, Identity>( + " +select identity_public_id, data, i.created_at +from identity i +join identity_email ie using (identity_id) +join email e using (email_id) +where ((? is null) or (i.identity_public_id = ?)) +and ((? is null) or (e.address = ?));", + ) + .bind(id) + .bind(id) + .bind(email) + .bind(email) + .fetch_one(&self.pool) + .await + { + Ok(i) => Some(i), + Err(sqlx::Error::RowNotFound) => None, + Err(e) => return Err(StoreError::SqlxError(e)), + }, + ) + } +} -- cgit v1.2.3