diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/client/mod.rs | 81 | ||||
| -rw-r--r-- | src/client/sqldb.rs | 172 | ||||
| -rw-r--r-- | src/ipl/authn.rs | 1 | ||||
| -rw-r--r-- | src/ipl/authz.rs | 1 | ||||
| -rw-r--r-- | src/ipl/mod.rs | 2 | ||||
| -rw-r--r-- | src/lib.rs | 171 | ||||
| -rw-r--r-- | src/main.old | 112 | ||||
| -rw-r--r-- | src/util/mod.rs | 15 |
8 files changed, 0 insertions, 555 deletions
diff --git a/src/client/mod.rs b/src/client/mod.rs deleted file mode 100644 index bb32e2c..0000000 --- a/src/client/mod.rs +++ /dev/null @@ -1,81 +0,0 @@ -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<OauthClientId>, - // ) -> Result<String, StoreError>; - - // 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<T, StoreError>; - // fn write_email_challenge(&self) -> Result<T, StoreError>; - - 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<Uuid, StoreError>; - - async fn find_identity( - &self, - id: Option<&Uuid>, - email: Option<&str>, - ) -> Result<Option<Identity>, StoreError>; - async fn write_identity(&self, i: &Identity) -> Result<(), StoreError>; - async fn read_identity(&self, id: &Uuid) -> Result<Identity, StoreError>; - - // fn read_sms_challenge(&self) -> Result<T, StoreError>; - // fn write_sms_challenge(&self) -> Result<T, StoreError>; -} - -// #[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 deleted file mode 100644 index 6ad0cc1..0000000 --- a/src/client/sqldb.rs +++ /dev/null @@ -1,172 +0,0 @@ -use log::{debug, error}; -use uuid::Uuid; - -use crate::{util, Identity}; - -use super::{Store, StoreError}; - -pub struct SqliteClient { - pool: sqlx::Pool<sqlx::Sqlite>, -} - -impl SqliteClient { - pub async fn new(pool: sqlx::Pool<sqlx::Sqlite>) -> 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<i64, StoreError> { - 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<i64, StoreError> { - 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<Uuid, StoreError> { - 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<Identity, StoreError> { - 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<Option<Identity>, 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)), - }, - ) - } -} diff --git a/src/ipl/authn.rs b/src/ipl/authn.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/ipl/authn.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/ipl/authz.rs b/src/ipl/authz.rs deleted file mode 100644 index f2f23e4..0000000 --- a/src/ipl/authz.rs +++ /dev/null @@ -1 +0,0 @@ -// TODO: Authorization suite diff --git a/src/ipl/mod.rs b/src/ipl/mod.rs deleted file mode 100644 index 1946995..0000000 --- a/src/ipl/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod authn; -pub mod authz; diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 7856d5c..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,171 +0,0 @@ -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(()) - } -} diff --git a/src/main.old b/src/main.old deleted file mode 100644 index e9d36c5..0000000 --- a/src/main.old +++ /dev/null @@ -1,112 +0,0 @@ -mod api; -mod client; -mod service; -mod util; - -use std::error::Error; - -use client::sqldb::PostgresClient; -use env_logger::Env; -use service::authn::Authn; -use sqlx::postgres::PgPoolOptions; - -#[async_std::main] -async fn main() -> Result<(), Box<dyn Error>> { - env_logger::Builder::from_env(Env::default().default_filter_or("debug")).init(); - - // Load configuration - // which DB do you want to use? - // what is the connection string (e.g. location, pass, etc...)? - - let pool = PgPoolOptions::new() - .max_connections(5) - .connect("postgres://secduser:p4ssw0rd@localhost:5419/secd") - .await?; - - sqlx::migrate!("store/sql/migrations").run(&pool).await?; - - // there are a few routes - // the service itself just provides some local functions which may be wrapped in a server. - // if you want to use the server, then you start the java/python/rust/ruby/go/etc... server - // otherwise, you just bring in the java/python/rust/ruby/go/etc... client - // also...maybe a terraform template to launch a _minimal_ auth server - // with your choice of RDS, dynamo, bigquery, or even local sqlite... - - // obviously need to configure terraform things... - - // if using the server, then you need to configure a few things: - // oauth endpoint with response_type, client_id - - // scratch - let pg_client = Box::new(PostgresClient::new(pool)); - let authn = Authn { store: pg_client }; - - ////////////////////////////////////////////////// - // CREATE NEW IDENTITY // which would be saved by the client - let identity = authn.register_identity().await?; - - ////////////////////////////////////////////////// - // Register a new oauth provider with some secrets, redirect, ids, etc... - authn - .register_oauth_provider( - api::OauthProvider::Google, - format!("client_id_{}", "CLIENT_SECRET_123"), - format!("client_secret_{}", util::generate_random_url_safe(4)), - "https://iam.SOMESITE.com/goauth...provided by default or customized".to_string(), - ) - .await?; - - ////////////////////////////////////////////////// - // Start oauth challenge and return the appropriate location. - let loc = authn - .initiate_oauth_challenge(identity, api::OauthProvider::Google) - .await?; - - ////////////////////////////////////////////////// - // Complete oauth challenge and return a session token - // let session = authn - // .complete_oauth_challenge(identity, api::OauthProvider::Google, state, access_token, expires_at, raw); - - ////////////////////////////////////////////////// - // Start email challenge - // authn.initiate_email_challenge(identity, email_address); - - ////////////////////////////////////////////////// - // Complete email challenge - // let session = authn.complete_email_challenge(email_address, code); - - ////////////////////////////////////////////////// - // Start SMS challenge - // authn.initiate_sms_challenge(identity, phone_number); - - ////////////////////////////////////////////////// - // Complete SMS challenge - // let session = authn.complete_sms_challenge(phone_number, code); - - ////////////////////////////////////////////////// - // Validate credentials - // let session = authn.validate(username, passphrase); - - ////////////////////////////////////////////////// - // Revoke session - // authn.revoke_session(token); - - ////////////////////////////////////////////////// - // Create API key - // let pub, priv = authn.generate_api_key(identity, Some(expires_at)); - - ////////////////////////////////////////////////// - // Revoke API key - // authn.revoke_api_key(pub, priv); - - ////////////////////////////////////////////////// - // Revoke identity - // authn.revoke_identity(identity); - - println!("Oauth2.0 URL: {}", loc); - - Ok(()) -} - -// TODO: oauth flow -// TODO: email flow diff --git a/src/util/mod.rs b/src/util/mod.rs deleted file mode 100644 index c939b95..0000000 --- a/src/util/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -use log::error; -use rand::distributions::Alphanumeric; -use rand::{thread_rng, Rng}; - -pub fn log_err(e: sqlx::Error) -> sqlx::Error { - error!("{:?}", e); - e -} -pub fn generate_random_url_safe(n: usize) -> String { - thread_rng() - .sample_iter(&Alphanumeric) - .take(n) - .map(char::from) - .collect() -} |
