diff options
Diffstat (limited to 'crates/secd/src/lib.rs')
| -rw-r--r-- | crates/secd/src/lib.rs | 183 |
1 files changed, 150 insertions, 33 deletions
diff --git a/crates/secd/src/lib.rs b/crates/secd/src/lib.rs index 15615b2..54759a5 100644 --- a/crates/secd/src/lib.rs +++ b/crates/secd/src/lib.rs @@ -2,8 +2,10 @@ pub mod auth; mod client; mod util; +use async_trait::async_trait; +use auth::z::Relationship; use client::{ - email::{EmailMessenger, EmailMessengerError, LocalMailer}, + email::{EmailMessenger, EmailMessengerError, LocalMailer, Sendgrid}, spice::{Spice, SpiceError}, store::{ sql_db::{PgClient, SqliteClient}, @@ -11,28 +13,40 @@ use client::{ }, }; use email_address::EmailAddress; -use log::{error, info}; +use lettre::message::Mailbox; +use log::{error, info, warn}; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DisplayFromStr}; -use std::{env::var, str::FromStr, sync::Arc}; +use serde_with::serde_as; +use std::{ + env::{set_var, var}, + str::FromStr, + sync::Arc, +}; use strum_macros::{Display, EnumString, EnumVariantNames}; use time::OffsetDateTime; -use url::Url; +use util::crypter::{Crypter, CrypterError}; use uuid::Uuid; pub const ENV_AUTH_STORE_CONN_STRING: &str = "SECD_AUTH_STORE_CONN_STRING"; +pub const ENV_CRYPTER_SECRET_KEY: &str = "SECD_CRYPTER_SECRET_KEY"; +pub const ENV_EMAIL_ADDRESS_FROM: &str = "SECD_EMAIL_ADDRESS_FROM"; +pub const ENV_EMAIL_ADDRESS_REPLYTO: &str = "SECD_EMAIL_ADDRESS_REPLYTO"; pub const ENV_EMAIL_MESSENGER: &str = "SECD_EMAIL_MESSENGER"; pub const ENV_EMAIL_MESSENGER_CLIENT_ID: &str = "SECD_EMAIL_MESSENGER_CLIENT_ID"; pub const ENV_EMAIL_MESSENGER_CLIENT_SECRET: &str = "SECD_EMAIL_MESSENGER_CLIENT_SECRET"; +pub const ENV_EMAIL_SENDGRID_API_KEY: &str = "SECD_EMAIL_SENDGRID_API_KEY"; +pub const ENV_EMAIL_SIGNIN_MESSAGE: &str = "SECD_EMAIL_SIGNIN_MESSAGE"; +pub const ENV_EMAIL_SIGNUP_MESSAGE: &str = "SECD_EMAIL_SIGNUP_MESSAGE"; pub const ENV_SPICE_SECRET: &str = "SECD_SPICE_SECRET"; pub const ENV_SPICE_SERVER: &str = "SECD_SPICE_SERVER"; -const SESSION_SIZE_BYTES: usize = 32; -const SESSION_DURATION: i64 = 60 /* seconds*/ * 60 /* minutes */ * 24 /* hours */ * 360 /* days */; -const EMAIL_VALIDATION_DURATION: i64 = 60 /* seconds*/ * 15 /* minutes */; const ADDRESSS_VALIDATION_CODE_SIZE: u8 = 6; const ADDRESS_VALIDATION_ALLOWS_ATTEMPTS: u8 = 5; const ADDRESS_VALIDATION_IDENTITY_SURJECTION: bool = true; +const CRYPTER_SECRET_KEY_DEFAULT: &str = "sup3rs3cr3t"; +const EMAIL_VALIDATION_DURATION: i64 = 60 /* seconds*/ * 15 /* minutes */; +const SESSION_DURATION: i64 = 60 /* seconds*/ * 60 /* minutes */ * 24 /* hours */ * 360 /* days */; +const SESSION_SIZE_BYTES: usize = 32; pub type AddressId = Uuid; pub type AddressValidationId = Uuid; @@ -45,10 +59,17 @@ pub type SessionToken = String; #[derive(Debug, derive_more::Display, thiserror::Error)] pub enum SecdError { + // AuthenticationError(AuthnError); + // AuthorizationError(AuthzError); + // // InitializationError(...) AddressValidationFailed, AddressValidationSessionExchangeFailed, AddressValidationExpiredOrConsumed, + CredentialAlreadyExists, + + CrypterError(#[from] CrypterError), + TooManyIdentities, IdentityNotFound, @@ -69,9 +90,62 @@ pub enum SecdError { } pub struct Secd { - store: Arc<dyn Store + Send + Sync + 'static>, + crypter: Crypter, email_messenger: Arc<dyn EmailMessenger + Send + Sync + 'static>, spice: Option<Arc<Spice>>, + store: Arc<dyn Store + Send + Sync + 'static>, + cfg: Cfg, +} + +struct Cfg { + email_address_from: Option<Mailbox>, + email_address_replyto: Option<Mailbox>, + email_signup_message: Option<String>, + email_signin_message: Option<String>, +} + +#[async_trait] +pub trait Authentication { + async fn validate_address( + &self, + address_type: AddressType, + identity_id: Option<IdentityId>, + ) -> Result<AddressValidation, SecdError>; + + async fn complete_address_validation( + &self, + validation_id: &AddressValidationId, + plaintext_token: Option<String>, + plaintext_code: Option<String>, + ) -> Result<AddressValidation, SecdError>; + + async fn create_credential( + &self, + t: &CredentialType, + identity_id: Option<IdentityId>, + ) -> Result<IdentityId, SecdError>; + // async fn update_credential(&self, t: &CredentialType) -> Result<(), SecdError>; + async fn reset_credential( + &self, + t: &CredentialType, + address: &AddressType, + ) -> Result<Credential, SecdError>; + async fn validate_credential(&self, t: &CredentialType) -> Result<Credential, SecdError>; + + // async fn expire_session_chain(&self, t: &SessionToken) -> Result<(), SecdError>; + // async fn expire_sessions(&self, i: &IdentityId) -> Result<(), SecdError>; + + // async fn get_identity(&self, t: &SessionToken) -> Result<Identity, SecdError>; + // async fn get_session(&self, t: &SessionToken) -> Result<Session, SecdError>; +} + +#[async_trait] +pub trait Authorization { + async fn check(&self, r: &Relationship) -> Result<bool, SecdError>; + async fn expand(&self) -> Result<(), SecdError>; + async fn read(&self) -> Result<(), SecdError>; + async fn watch(&self) -> Result<(), SecdError>; + async fn write(&self, relationships: &[Relationship]) -> Result<(), SecdError>; } #[derive(Display, Debug, Serialize, Deserialize, EnumString, EnumVariantNames)] @@ -136,34 +210,53 @@ pub enum AddressType { Sms { phone_number: Option<PhoneNumber> }, } +#[serde_as] #[derive(Debug, Serialize)] pub struct Credential { pub id: CredentialId, pub identity_id: IdentityId, pub t: CredentialType, + #[serde(with = "time::serde::timestamp")] + pub created_at: OffsetDateTime, + #[serde(with = "time::serde::timestamp::option")] + pub revoked_at: Option<OffsetDateTime>, + #[serde(with = "time::serde::timestamp::option")] + pub deleted_at: Option<OffsetDateTime>, } #[serde_as] -#[derive(Debug, Serialize)] +#[derive(Debug, Display, Serialize, Deserialize, EnumString)] pub enum CredentialType { - Passphrase { - key: String, - value: String, - }, - Oicd { - value: String, - }, - OneTimeCodes { - codes: Vec<String>, - }, - Totp { - #[serde_as(as = "DisplayFromStr")] - url: Url, - code: String, - }, - WebAuthn { - value: String, - }, + Passphrase { key: String, value: String }, + Oidc { value: String }, + OneTimeCodes { codes: Vec<String> }, + // Totp { + // #[serde_as(as = "DisplayFromStr")] + // url: Url, + // code: String, + // }, + WebAuthn { value: String }, +} + +struct SecuredCredential { + pub id: CredentialId, + pub identity_id: IdentityId, + pub t: CredentialType, + pub created_at: OffsetDateTime, + pub revoked_at: Option<OffsetDateTime>, + pub deleted_at: Option<OffsetDateTime>, +} + +enum SecuredCredentialType { + Passphrase { key: String, value: String }, + Oidc { value: String }, + OneTimeCodes { codes: Vec<String> }, + // Totp { + // #[serde_as(as = "DisplayFromStr")] + // url: Url, + // code: String, + // }, + WebAuthn { value: String }, } #[serde_with::skip_serializing_none] @@ -207,12 +300,29 @@ impl Secd { &var(ENV_EMAIL_MESSENGER).unwrap_or(AuthEmailMessenger::Local.to_string()), ) .expect("unreachable f4ad0f48-0812-427f-b477-0f9c67bb69c5"); - let email_messenger_client_id = var(ENV_EMAIL_MESSENGER_CLIENT_ID).ok(); - let email_messenger_client_secret = var(ENV_EMAIL_MESSENGER_CLIENT_SECRET).ok(); + let crypter_secret_key = var(ENV_CRYPTER_SECRET_KEY).unwrap_or_else(|_| { + warn!( + "NO CRYPTER KEY PROVIDED, USING DEFAULT KEY. DO NOT USE THIS KEY IN PRODUCTION. PROVIDE A UNIQUE SECRET KEY BY SETTING THE ENVIORNMENT VARIABLE {}. THE DEFAULT KEY IS: {}", + ENV_CRYPTER_SECRET_KEY, + CRYPTER_SECRET_KEY_DEFAULT, + ); + CRYPTER_SECRET_KEY_DEFAULT.to_string() + }); info!("starting client with auth_store: {:?}", auth_store); info!("starting client with email_messenger: {:?}", auth_store); + let cfg = Cfg { + email_address_from: var(ENV_EMAIL_ADDRESS_FROM) + .ok() + .and_then(|s| s.parse().ok()), + email_address_replyto: var(ENV_EMAIL_ADDRESS_REPLYTO) + .ok() + .and_then(|s| s.parse().ok()), + email_signup_message: var(ENV_EMAIL_SIGNUP_MESSAGE).ok(), + email_signin_message: var(ENV_EMAIL_SIGNIN_MESSAGE).ok(), + }; + let store = match auth_store { AuthStore::Sqlite { conn } => { if z_schema.is_some() { @@ -252,7 +362,10 @@ impl Secd { }; let email_sender = match email_messenger { - AuthEmailMessenger::Local => LocalMailer {}, + AuthEmailMessenger::Local => LocalMailer::new(), + AuthEmailMessenger::Sendgrid => Sendgrid::new( + var(ENV_EMAIL_SENDGRID_API_KEY).expect("No SENDGRID_API_KEY provided"), + ), _ => unimplemented!(), }; @@ -267,10 +380,14 @@ impl Secd { None => None, }; + let crypter = Crypter::new(crypter_secret_key.as_bytes()); + Ok(Secd { - store, - email_messenger: Arc::new(email_sender), + crypter, + email_messenger: email_sender, spice, + store, + cfg, }) } } |
