diff options
Diffstat (limited to 'src/lib.rs')
| -rw-r--r-- | src/lib.rs | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..7856d5c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,171 @@ +mod client; +mod util; + +use std::sync::Arc; + +use client::{sqldb::SqliteClient, Store, StoreError}; +use derive_more::Display; +use email_address::EmailAddress; +use log::{error, info}; +use serde::Serialize; +use time::OffsetDateTime; +use uuid::Uuid; + +#[derive(Copy, Display, Clone, Debug)] +pub enum OauthProvider { + Amazon, + Apple, + Dropbox, + Facebook, + Github, + Gitlab, + Google, + Instagram, + LinkedIn, + Microsoft, + Paypal, + Reddit, + Spotify, + Strava, + Stripe, + Twitch, + Twitter, + WeChat, +} + +#[derive(Display, Debug)] +pub enum AuthStore { + Sqlite, + Postgres, + MySql, + Mongo, + Dynamo, + Redis, +} + +pub type OauthClientId = String; +pub type OauthClientSecretEncrypted = String; +pub type OauthConsentUri = String; + +pub type IdentityId = Uuid; + +////////////////////////////////////////////////// +// Resources +#[derive(sqlx::FromRow, Debug, Serialize)] +pub struct Identity { + #[sqlx(rename = "identity_public_id")] + id: Uuid, + created_at: sqlx::types::time::OffsetDateTime, + #[serde(skip_serializing_if = "Option::is_none")] + data: Option<String>, +} + +pub struct ApiKey {} + +pub struct Session {} + +pub struct ValidationRequest {} +////////////////////////////////////////////////// +#[derive(Debug, derive_more::Display, thiserror::Error)] +pub enum SecdError { + InvalidEmailAddress, + SqliteInitializationFailure(sqlx::Error), + StoreError(#[from] StoreError), + EmailValidationRequestError, + Unknown, +} + +pub struct Secd { + store: Arc<dyn Store + Send + Sync + 'static>, +} + +impl Secd { + pub async fn init( + auth_store: AuthStore, + conn_string: Option<String>, + // TODO: Turn Secd into a trait and impl separately. + // TODO: initialize email and SMS templates with secd + ) -> Result<Self, SecdError> { + let store = match auth_store { + AuthStore::Sqlite => SqliteClient::new( + sqlx::sqlite::SqlitePoolOptions::new() + .connect(conn_string.unwrap_or("sqlite::memory:".into()).as_str()) + .await + .map_err(|e| SecdError::SqliteInitializationFailure(e))?, + ), + // TODO: if AuthStore is provided, then configure the client. + _ => return Err(SecdError::Unknown), + } + .await; + + Ok(Secd { + store: Arc::new(store), + }) + } + + pub async fn create_identity(&self) -> Result<Uuid, SecdError> { + let id = Uuid::new_v4(); + self.store + .write_identity(&Identity { + id, + created_at: OffsetDateTime::now_utc(), + data: None, + }) + .await?; + + Ok(id) + } + + pub async fn create_validation_request(&self, email: Option<&str>) -> Result<(), SecdError> { + // TODO: refactor based on email, phone, or some other template? Or break up the API? + let email = match email { + Some(ea) => { + if EmailAddress::is_valid(ea) { + ea + } else { + return Err(SecdError::InvalidEmailAddress); + } + } + None => return Err(SecdError::InvalidEmailAddress), + }; + + match self.store.find_identity(None, Some(email)).await? { + Some(identity) => { + error!("TODO: implement email send with LOGIN template"); + error!("TODO: send to: {}", email); + let req_id = self + .store + .write_email_validation_request(identity.id, email) + .await?; + + // TODO: provide some dummy email handlers that are used when testing locally... + error!("TODO: when the request comes back, it needs to hit something like /iam/identity/1234/email-validation/1234?code=2345"); + error!("TODO: consequently, we may want to shorten the url by providing a quick access code and/or /iam/email-validation/1234/validate"); + } + None => { + let identity = Identity { + id: Uuid::new_v4(), + created_at: OffsetDateTime::now_utc(), + data: None, + }; + self.store.write_identity(&identity).await?; + self.store.write_email(identity.id, email).await?; + error!("TODO: implement email send with SIGN_UP template"); + self.store + .write_email_validation_request(identity.id, email) + .await?; + } + } + + error!("TODO: think about returning the identity id for which this validation request was created"); + Ok(()) + } + + pub async fn get_identity(&self, id: IdentityId) -> Result<Identity, SecdError> { + Ok(self.store.read_identity(&id).await?) + } + + pub async fn create_email_validation(email: String) -> Result<(), SecdError> { + Ok(()) + } +} |
