aboutsummaryrefslogtreecommitdiff
path: root/crates/secd/src/command
diff options
context:
space:
mode:
authorbenj <benj@rse8.com>2022-12-30 15:57:36 -0800
committerbenj <benj@rse8.com>2022-12-30 15:57:36 -0800
commit8ca3433b2a4a82723e00e64b1e5aff0b1bed95b3 (patch)
tree1ff85fd9fbd94a5559f9dbac755973fd58b31f28 /crates/secd/src/command
parentf0ea9ecd17b03605d747044874a26e1bd52c0ee1 (diff)
downloadsecdiam-8ca3433b2a4a82723e00e64b1e5aff0b1bed95b3.tar
secdiam-8ca3433b2a4a82723e00e64b1e5aff0b1bed95b3.tar.gz
secdiam-8ca3433b2a4a82723e00e64b1e5aff0b1bed95b3.tar.bz2
secdiam-8ca3433b2a4a82723e00e64b1e5aff0b1bed95b3.tar.lz
secdiam-8ca3433b2a4a82723e00e64b1e5aff0b1bed95b3.tar.xz
secdiam-8ca3433b2a4a82723e00e64b1e5aff0b1bed95b3.tar.zst
secdiam-8ca3433b2a4a82723e00e64b1e5aff0b1bed95b3.zip
impl authZ write and check (depends on spicedb for now)
Diffstat (limited to 'crates/secd/src/command')
-rw-r--r--crates/secd/src/command/authn.rs287
-rw-r--r--crates/secd/src/command/mod.rs74
2 files changed, 0 insertions, 361 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(())
- }
-}
diff --git a/crates/secd/src/command/mod.rs b/crates/secd/src/command/mod.rs
deleted file mode 100644
index c14cf6c..0000000
--- a/crates/secd/src/command/mod.rs
+++ /dev/null
@@ -1,74 +0,0 @@
-pub mod authn;
-
-use super::{AuthEmailMessenger, AuthStore, Secd, SecdError};
-use crate::{
- client::{
- email,
- store::sql_db::{PgClient, SqliteClient},
- },
- ENV_AUTH_STORE_CONN_STRING, ENV_EMAIL_MESSENGER, ENV_EMAIL_MESSENGER_CLIENT_ID,
- ENV_EMAIL_MESSENGER_CLIENT_SECRET,
-};
-use log::{error, info};
-use std::{env::var, str::FromStr, sync::Arc};
-
-impl Secd {
- /// init
- ///
- /// Initialize SecD with the specified configuration, established the necessary
- /// constraints, persistance stores, and options.
- pub async fn init() -> Result<Self, SecdError> {
- let auth_store = AuthStore::from(var(ENV_AUTH_STORE_CONN_STRING).ok());
- let email_messenger = AuthEmailMessenger::from_str(
- &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();
-
- info!("starting client with auth_store: {:?}", auth_store);
- info!("starting client with email_messenger: {:?}", auth_store);
-
- let store = match auth_store {
- AuthStore::Sqlite { conn } => {
- SqliteClient::new(
- sqlx::sqlite::SqlitePoolOptions::new()
- .connect(&conn)
- .await
- .map_err(|e| {
- SecdError::StoreInitFailure(format!("failed to init sqlite: {}", e))
- })?,
- )
- .await
- }
- AuthStore::Postgres { conn } => {
- PgClient::new(
- sqlx::postgres::PgPoolOptions::new()
- .connect(&conn)
- .await
- .map_err(|e| {
- SecdError::StoreInitFailure(format!("failed to init sqlite: {}", e))
- })?,
- )
- .await
- }
- rest @ _ => {
- error!(
- "requested an AuthStore which has not yet been implemented: {:?}",
- rest
- );
- unimplemented!()
- }
- };
-
- let email_sender = match email_messenger {
- AuthEmailMessenger::Local => email::LocalMailer {},
- _ => unimplemented!(),
- };
-
- Ok(Secd {
- store,
- email_messenger: Arc::new(email_sender),
- })
- }
-}