aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs171
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(())
+ }
+}