diff options
| author | benj <benj@rse8.com> | 2023-05-22 15:47:06 -0700 |
|---|---|---|
| committer | benj <benj@rse8.com> | 2023-05-22 15:47:06 -0700 |
| commit | ed34a5251f13bbded0aa15719887db4924b351eb (patch) | |
| tree | 9719d805e915f4483d5db3e5e612e8b4cf5c702c /crates/secd/src/auth/n.rs | |
| parent | eb92f823c31a5e702af7005231f0d6915aad3342 (diff) | |
| download | secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.gz secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.bz2 secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.lz secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.xz secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.zst secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.zip | |
update credential API to include sessions
This change updates the credential API to include sessions as just another
credential type. It adds the ApiToken type and enables revocation of
credentials. Updates were also made to the Identity API which now includes a
list of new credentials added to an Identity.
This change also migrates off the hacky ENV configuration paradigm and includes
a new config.toml file specified by the SECD_CONFIG_PATH env var. No default is
currently provided.
Clippy updates and code cleanup.
Diffstat (limited to '')
| -rw-r--r-- | crates/secd/src/auth/n.rs | 203 |
1 files changed, 151 insertions, 52 deletions
diff --git a/crates/secd/src/auth/n.rs b/crates/secd/src/auth/n.rs index 1f32fd6..dde6e7d 100644 --- a/crates/secd/src/auth/n.rs +++ b/crates/secd/src/auth/n.rs @@ -5,12 +5,12 @@ use crate::{ DEFAULT_SIGNUP_EMAIL, }, store::{ - AddressLens, AddressValidationLens, CredentialLens, IdentityLens, SessionLens, - Storable, StoreError, + AddressLens, AddressValidationLens, CredentialLens, IdentityLens, Storable, StoreError, }, }, - util, Address, AddressType, AddressValidation, AddressValidationId, AddressValidationMethod, - Credential, CredentialType, Identity, IdentityId, Secd, SecdError, Session, SessionToken, + util::{self, ErrorContext}, + Address, AddressType, AddressValidation, AddressValidationId, AddressValidationMethod, + Credential, CredentialId, CredentialType, Identity, IdentityId, Secd, SecdError, ADDRESSS_VALIDATION_CODE_SIZE, ADDRESS_VALIDATION_ALLOWS_ATTEMPTS, ADDRESS_VALIDATION_IDENTITY_SURJECTION, EMAIL_VALIDATION_DURATION, }; @@ -84,8 +84,8 @@ impl Secd { revoked_at: None, validated_at: None, attempts: 0, - hashed_token: util::hash(&secret.as_bytes()), - hashed_code: util::hash(&code.as_bytes()), + hashed_token: util::hash(secret.as_bytes()), + hashed_code: util::hash(code.as_bytes()), }; validation.write(self.store.clone()).await?; @@ -95,11 +95,13 @@ impl Secd { .cfg .email_address_from .clone() + .and_then(|s| s.parse().ok()) .unwrap_or("SecD <noreply@secd.com>".parse().unwrap()), replyto_address: self .cfg .email_address_replyto .clone() + .and_then(|s| s.parse().ok()) .unwrap_or("SecD <noreply@secd.com>".parse().unwrap()), recipient: email_address.clone(), subject: "Login Request".into(), @@ -129,7 +131,7 @@ impl Secd { validation_id: &AddressValidationId, plaintext_token: Option<String>, plaintext_code: Option<String>, - ) -> Result<Session, SecdError> { + ) -> Result<Credential, SecdError> { let mut validation = AddressValidation::find( self.store.clone(), &AddressValidationLens { @@ -193,7 +195,6 @@ impl Secd { id: None, address_type: Some(&validation.address.t), validated_address: Some(true), - session_token_hash: None, }, ) .await?; @@ -210,6 +211,7 @@ impl Secd { id: Uuid::new_v4(), address_validations: vec![], credentials: vec![], + new_credentials: vec![], rules: vec![], metadata: None, created_at: OffsetDateTime::now_utc(), @@ -235,30 +237,72 @@ impl Secd { 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]"))?; + let mut session = Credential::new_session(validation.identity_id.expect("unreachable d3ded289-72eb-4a42-a37d-f5c9c697cc61 [assert(identity.is_some()) prevents this]"))?; + let plaintext_type = session.t.clone(); + + session.hash(&self.crypter)?; session.write(self.store.clone()).await?; + session.t = plaintext_type; + Ok(session) } + pub async fn create_identity_with_credential( + &self, + t: CredentialType, + identity_id: IdentityId, + metadata: Option<String>, + ) -> Result<Identity, SecdError> { + let identity = Identity::find( + self.store.clone(), + &IdentityLens { + id: Some(&identity_id), + address_type: None, + validated_address: None, + }, + ) + .await?; + + if !identity.is_empty() { + log::error!("identity was found while creating a new identity with a credential"); + return Err(SecdError::IdentityAlreadyExists); + } + + Identity { + id: identity_id, + address_validations: vec![], + credentials: vec![], + new_credentials: vec![], + rules: vec![], + metadata, + created_at: OffsetDateTime::now_utc(), + deleted_at: None, + } + .write(self.store.clone()) + .await?; + + self.create_credential(t, Some(identity_id), None).await + } + pub async fn create_credential( &self, t: CredentialType, identity_id: Option<IdentityId>, + expires_at: Option<OffsetDateTime>, ) -> Result<Identity, SecdError> { - let identity = match identity_id { + let mut identity = match identity_id { Some(id) => Identity::find( self.store.clone(), &IdentityLens { id: Some(&id), address_type: None, validated_address: None, - session_token_hash: None, }, ) .await? .into_iter() - .nth(0) + .next() .ok_or(SecdError::IdentityNotFound)?, None => { @@ -266,6 +310,7 @@ impl Secd { id: Uuid::new_v4(), address_validations: vec![], credentials: vec![], + new_credentials: vec![], rules: vec![], metadata: None, created_at: OffsetDateTime::now_utc(), @@ -282,7 +327,6 @@ impl Secd { id: None, identity_id: Some(identity.id), t: Some(&t), - restrict_by_key: Some(false), }, ) .await?[..] @@ -290,9 +334,9 @@ impl Secd { [] => Credential { id: Uuid::new_v4(), identity_id: identity.id, - t, + t: t.clone(), created_at: OffsetDateTime::now_utc(), - revoked_at: None, + revoked_at: expires_at, deleted_at: None, }, _ => return Err(SecdError::CredentialAlreadyExists), @@ -307,57 +351,100 @@ impl Secd { err => SecdError::StoreError(err), })?; - Ok(identity) - } + identity.new_credentials.push(Credential { + id: credential.id, + identity_id: credential.identity_id, + t, + created_at: credential.created_at, + revoked_at: credential.revoked_at, + deleted_at: credential.deleted_at, + }); - pub async fn validate_credential( - &self, - // t: CredentialType, - // key: String, - // value: Option<String>, - ) -> Result<Session, SecdError> { - // Credential::find(store, lens) use key here as unique index - todo!() + Ok(identity) } - pub async fn get_session(&self, t: &SessionToken) -> Result<Session, SecdError> { - let token = hex::decode(t)?; - let mut session = Session::find( + pub async fn validate_credential(&self, t: CredentialType) -> Result<Credential, SecdError> { + let mut retrieved = Credential::find( self.store.clone(), - &SessionLens { - token_hash: Some(&util::hash(&token)), + &CredentialLens { + id: None, identity_id: None, + t: Some(&t), }, ) - .await?; - assert!(session.len() <= 1, "get session failed: multiple sessions found for a single token. This is very _very_ bad."); + .await? + .into_iter() + .next() + .ok_or(SecdError::InvalidCredential)?; - if session.is_empty() { - return Err(SecdError::InvalidSession); - } else { - let mut session = session.swap_remove(0); - session.token = token; - Ok(session) - } + match retrieved.revoked_at { + Some(t) if t <= OffsetDateTime::now_utc() => { + log::debug!("credential was revoked"); + Err(SecdError::InvalidCredential) + } + _ => Ok(()), + }?; + + match retrieved.deleted_at { + Some(t) if t <= OffsetDateTime::now_utc() => { + log::debug!("credential was deleted"); + Err(SecdError::InvalidCredential) + } + _ => Ok(()), + }?; + + retrieved.hash_compare(&t, &self.crypter)?; + + // Return the initially provided plaintext credential since it's valid + retrieved.t = t; + + Ok(retrieved) } pub async fn get_identity( &self, i: Option<IdentityId>, - t: Option<SessionToken>, + t: Option<CredentialType>, ) -> Result<Identity, SecdError> { - let token_hash = match t { - Some(tok) => Some(util::hash(&hex::decode(&tok)?)), - None => None, - }; + if i.is_none() && t.is_none() { + log::error!("get_identity expects that at least one of IdentityId or CredentialType is provided. None were found."); + return Err(SecdError::IdentityNotFound); + } + + let c = Credential::find( + self.store.clone(), + &CredentialLens { + id: None, + identity_id: i, + t: t.as_ref(), + }, + ) + .await?; + + assert!( + c.len() <= 1, + "The provided credential refers to more than one identity. This is very _very_ bad." + ); + let identity_id = c + .into_iter() + .next() + .ok_or(SecdError::InvalidCredential) + .ctx("No identities were found for the provided identity_id and credential_type")? + .identity_id; + + if i.is_some() && i != Some(identity_id) { + log::error!( + "The provided identity does not match the identity associated with this credential" + ); + return Err(SecdError::InvalidCredential); + } let mut i = Identity::find( self.store.clone(), &IdentityLens { - id: i.as_ref(), + id: Some(&identity_id), address_type: None, validated_address: None, - session_token_hash: token_hash, }, ) .await?; @@ -368,7 +455,7 @@ impl Secd { ); if i.is_empty() { - return Err(SecdError::IdentityNotFound); + Err(SecdError::IdentityNotFound) } else { Ok(i.swap_remove(0)) } @@ -385,12 +472,11 @@ impl Secd { id: Some(&i), address_type: None, validated_address: None, - session_token_hash: None, }, ) .await? .into_iter() - .nth(0) + .next() .ok_or(SecdError::IdentityNotFound)?; identity.metadata = Some(md); @@ -399,9 +485,22 @@ impl Secd { Ok(identity) } - pub async fn revoke_session(&self, session: &mut Session) -> Result<(), SecdError> { - session.revoked_at = Some(OffsetDateTime::now_utc()); - session.write(self.store.clone()).await?; + pub async fn revoke_credential(&self, credential_id: CredentialId) -> Result<(), SecdError> { + let mut credential = Credential::find( + self.store.clone(), + &CredentialLens { + id: Some(credential_id), + identity_id: None, + t: None, + }, + ) + .await? + .into_iter() + .next() + .ok_or(SecdError::InvalidCredential)?; + + credential.revoked_at = Some(OffsetDateTime::now_utc()); + credential.write(self.store.clone()).await?; Ok(()) } } |
