diff options
Diffstat (limited to 'crates/secd/src/command/authn.rs')
| -rw-r--r-- | crates/secd/src/command/authn.rs | 287 |
1 files changed, 0 insertions, 287 deletions
diff --git a/crates/secd/src/command/authn.rs b/crates/secd/src/command/authn.rs deleted file mode 100644 index 1d3b2d5..0000000 --- a/crates/secd/src/command/authn.rs +++ /dev/null @@ -1,287 +0,0 @@ -use std::str::FromStr; - -use crate::{ - client::{ - email::{EmailValidationMessage, Sendable}, - store::{ - AddressLens, AddressValidationLens, IdentityLens, SessionLens, Storable, StoreError, - }, - }, - util, Address, AddressType, AddressValidation, AddressValidationId, AddressValidationMethod, - Credential, CredentialType, Identity, PhoneNumber, Secd, SecdError, Session, SessionToken, - ADDRESSS_VALIDATION_CODE_SIZE, ADDRESS_VALIDATION_ALLOWS_ATTEMPTS, - ADDRESS_VALIDATION_IDENTITY_SURJECTION, EMAIL_VALIDATION_DURATION, -}; -use email_address::EmailAddress; -use log::warn; -use rand::Rng; -use time::{Duration, OffsetDateTime}; -use uuid::Uuid; - -impl Secd { - pub async fn validate_email( - &self, - email_address: &str, - identity_id: Option<Uuid>, - ) -> Result<AddressValidation, SecdError> { - let email_address = EmailAddress::from_str(email_address)?; - // record address (idempotent operation) - let mut address = Address { - id: Uuid::new_v4(), - t: AddressType::Email { - email_address: Some(email_address.clone()), - }, - created_at: OffsetDateTime::now_utc(), - }; - - if let Err(StoreError::IdempotentCheckAlreadyExists) = - address.write(self.store.clone()).await - { - address = Address::find( - self.store.clone(), - &AddressLens { - id: None, - t: Some(&AddressType::Email { - email_address: Some(email_address.clone()), - }), - }, - ) - .await? - .into_iter() - .next() - .ok_or(SecdError::AddressValidationFailed)?; - } - - let secret = hex::encode(rand::thread_rng().gen::<[u8; 32]>()); - let code: String = vec![0; ADDRESSS_VALIDATION_CODE_SIZE as usize] - .into_iter() - .map(|_| char::from_digit(rand::thread_rng().gen_range(0..=9), 10).unwrap()) - .collect(); - - let mut validation = AddressValidation { - id: Uuid::new_v4(), - identity_id, - address, - method: AddressValidationMethod::Email, - created_at: OffsetDateTime::now_utc(), - expires_at: OffsetDateTime::now_utc() - .checked_add(Duration::new(EMAIL_VALIDATION_DURATION, 0)) - .ok_or(SecdError::Todo)?, - revoked_at: None, - validated_at: None, - attempts: 0, - hashed_token: util::hash(&secret.as_bytes()), - hashed_code: util::hash(&code.as_bytes()), - }; - - validation.write(self.store.clone()).await?; - - let msg =EmailValidationMessage { - recipient: email_address.clone(), - subject: "Confirm Your Email".into(), - body: format!("This is an email validation message. Click this link [{:?}?s={}] or use the code [{}]", validation.id, secret, code), - }; - - match msg.send().await { - Ok(_) => { /* TODO: Write down the message*/ } - Err(e) => { - validation.revoked_at = Some(OffsetDateTime::now_utc()); - validation.write(self.store.clone()).await?; - return Err(SecdError::EmailMessengerError(e)); - } - } - - Ok(validation) - } - pub async fn validate_sms( - &self, - phone_number: &PhoneNumber, - ) -> Result<AddressValidation, SecdError> { - todo!() - } - pub async fn complete_address_validation( - &self, - validation_id: &AddressValidationId, - plaintext_token: Option<String>, - plaintext_code: Option<String>, - ) -> Result<Session, SecdError> { - let mut validation = AddressValidation::find( - self.store.clone(), - &AddressValidationLens { - id: Some(validation_id), - }, - ) - .await? - .into_iter() - .next() - .ok_or(SecdError::AddressValidationFailed)?; - - if validation.validated_at.is_some() { - return Err(SecdError::AddressValidationExpiredOrConsumed); - } - - validation.attempts += 1; - if validation.attempts > ADDRESS_VALIDATION_ALLOWS_ATTEMPTS as i32 { - warn!( - "validation failed: Too many validation attempts were tried for validation {:?}", - validation.id - ); - validation.write(self.store.clone()).await?; - return Err(SecdError::AddressValidationExpiredOrConsumed); - } - - let hashed_token = plaintext_token.map(|s| util::hash(s.as_bytes())); - let hashed_code = plaintext_code.map(|c| util::hash(c.as_bytes())); - - let mut warn_msg = None; - match (hashed_token, hashed_code) { - (None, None) => { - warn_msg = Some("neither token nor hash was provided during the address validation session exchange"); - } - (Some(t), None) => { - if validation.hashed_token != t { - warn_msg = - Some("the provided token does not match the address validation token"); - } - } - (None, Some(c)) => { - if validation.hashed_code != c { - warn_msg = Some("the provided code does not match the address validation code"); - } - } - (Some(t), Some(c)) => { - if validation.hashed_token != t || validation.hashed_code != c { - warn_msg = Some("the provided token and code must both match the address validation token and code"); - } - } - }; - - if let Some(msg) = warn_msg { - warn!("validation failed: {}", msg); - validation.write(self.store.clone()).await?; - return Err(SecdError::AddressValidationSessionExchangeFailed); - } - - let identity = Identity::find( - self.store.clone(), - &IdentityLens { - id: None, - address_type: Some(&validation.address.t), - validated_address: Some(true), - session_token_hash: None, - }, - ) - .await?; - - if !ADDRESS_VALIDATION_IDENTITY_SURJECTION && identity.len() > 1 { - warn!("validation failed: identity validation surjection disallowed"); - validation.write(self.store.clone()).await?; - return Err(SecdError::TooManyIdentities); - } - - let mut identity = identity.into_iter().next(); - if identity.is_none() { - let i = Identity { - id: Uuid::new_v4(), - address_validations: vec![], - credentials: vec![], - rules: vec![], - metadata: None, - created_at: OffsetDateTime::now_utc(), - deleted_at: None, - }; - i.write(self.store.clone()).await?; - identity = Some(i); - } - - assert!(identity.is_some()); - - // If the validation was attached to another identity, unless surjection is allowed, it cannot be recorded. - if !ADDRESS_VALIDATION_IDENTITY_SURJECTION - && validation.identity_id.is_some() - && identity.as_ref().map(|i| i.id) != validation.identity_id - { - warn!("validation failed: identity validation surjection is disallowed, but found existing identity for another account"); - validation.write(self.store.clone()).await?; - return Err(SecdError::TooManyIdentities); - } - - validation.identity_id = identity.map(|i| i.id); - validation.validated_at = Some(OffsetDateTime::now_utc()); - validation.write(self.store.clone()).await?; - - let session = Session::new(validation.identity_id.expect("unreachable d3ded289-72eb-4a42-a37d-f5c9c697cc61 [assert(identity.is_some()) prevents this]"))?; - session.write(self.store.clone()).await?; - - Ok(session) - } - pub async fn create_credential( - &self, - t: CredentialType, - key: String, - value: Option<String>, - ) -> Result<Credential, SecdError> { - todo!() - } - - pub async fn validate_credential( - &self, - t: CredentialType, - key: String, - value: Option<String>, - ) -> Result<Session, SecdError> { - todo!() - } - - pub async fn get_session(&self, t: &SessionToken) -> Result<Session, SecdError> { - let token = hex::decode(t)?; - let mut session = Session::find( - self.store.clone(), - &SessionLens { - token_hash: Some(&util::hash(&token)), - identity_id: None, - }, - ) - .await?; - assert!(session.len() <= 1, "get session failed: multiple sessions found for a single token. This is very _very_ bad."); - - if session.is_empty() { - return Err(SecdError::InvalidSession); - } else { - let mut session = session.swap_remove(0); - session.token = token; - Ok(session) - } - } - - pub async fn get_identity(&self, i: &SessionToken) -> Result<Identity, SecdError> { - let token_hash = util::hash(&hex::decode(i)?); - let mut i = Identity::find( - self.store.clone(), - &IdentityLens { - id: None, - address_type: None, - validated_address: None, - session_token_hash: Some(token_hash), - }, - ) - .await?; - - assert!( - i.len() <= 1, - "The provided id refers to more than one identity. This is very _very_ bad." - ); - - if i.is_empty() { - return Err(SecdError::IdentityNotFound); - } else { - Ok(i.swap_remove(0)) - } - } - - pub async fn revoke_session(&self, session: &mut Session) -> Result<(), SecdError> { - session.revoked_at = Some(OffsetDateTime::now_utc()); - session.write(self.store.clone()).await?; - Ok(()) - } -} |
