merfolk_middleware_authentication 0.1.0

A `Middleware` for merfolk providing simple authentication.
Documentation
use std::marker::PhantomData;

use serde::{Deserialize, Serialize};

use merfolk::{
  interfaces::{Backend, Middleware},
  Call, Reply,
};

use anyhow::Result;
use thiserror::Error;

use wildmatch::WildMatch;

#[derive(Debug, Error)]
pub enum Error {
  #[error("could not find scope for procedure: {procedure}")]
  ScopeNotFound { procedure: String },
  #[error("auth was not provided")]
  NoAuth,
  #[error("authentication failed for {auth} in scope {scope:?}: {source}")]
  AuthenticationFailed {
    auth: String,
    scope: Option<String>,
    #[source]
    source: anyhow::Error,
  },
  #[error("no vaildator was provided during init()")]
  NoValidatorRegistered,
}

#[derive(derive_builder::Builder)]
#[builder(pattern = "owned")]
pub struct Authentication<'a, B> {
  #[builder(private, default = "PhantomData")]
  __phantom: PhantomData<B>,

  #[builder(setter(into, strip_option), default = "None")]
  scopes: Option<Vec<(String, String)>>,

  #[allow(clippy::type_complexity)]
  #[builder(setter(name = "authenticator_setter", strip_option), private, default = "None")]
  authenticator: Option<Box<dyn Fn((String, String), Vec<String>) -> Result<()> + 'a + Send>>,

  #[builder(setter(into, strip_option), default = "None")]
  auth: Option<(String, String)>,
}

impl<'a, B> AuthenticationBuilder<'a, B> {
  pub fn authenticator<A>(self, value: A) -> Self
  where
    A: Fn((String, String), Vec<String>) -> Result<()> + 'a + Send,
  {
    self.authenticator_setter(Box::new(value))
  }

  pub fn build_boxed(self) -> std::result::Result<Box<Authentication<'a, B>>, AuthenticationBuilderError> {
    self.build().map(Box::new)
  }
}

impl<'a, B> Authentication<'a, B> {
  pub fn builder() -> AuthenticationBuilder<'a, B> {
    AuthenticationBuilder::default()
  }
}

#[derive(Serialize, Deserialize)]
struct Intermediate<B: Backend> {
  auth: (String, String),
  payload: B::Intermediate,
}

impl<'staic, B: Backend + 'static> Middleware for Authentication<'static, B> {
  type Backend = B;

  fn wrap_call(&self, call: Result<crate::Call<<Self::Backend as Backend>::Intermediate>>) -> Result<crate::Call<<Self::Backend as Backend>::Intermediate>> {
    let auth = self.auth.as_ref().ok_or(Error::NoAuth)?;

    if let Ok(call) = call {
      Ok(Call {
        procedure: call.procedure,
        payload: B::serialize(&Intermediate::<B> {
          auth: (auth.0.to_string(), auth.1.to_string()),
          payload: call.payload,
        })?,
      })
    } else {
      call
    }
  }

  fn wrap_reply(&self, reply: Result<crate::Reply<<Self::Backend as Backend>::Intermediate>>) -> Result<crate::Reply<<Self::Backend as Backend>::Intermediate>> {
    reply
  }

  fn unwrap_call(&self, call: Result<crate::Call<<Self::Backend as Backend>::Intermediate>>) -> Result<crate::Call<<Self::Backend as Backend>::Intermediate>> {
    if let Ok(call) = call {
      let mut scope: Vec<String> = vec![];

      if let Some(scopes) = &self.scopes {
        scopes.iter().for_each(|s| {
          if WildMatch::new(&s.0).is_match(call.procedure.as_str()) {
            scope.push(s.1.to_string())
          };
        });
      }

      let intermediate: Intermediate<B> = B::deserialize(&call.payload)?;

      if let Err(err) = self.authenticator.as_ref().ok_or::<Error>(Error::NoValidatorRegistered)?(intermediate.auth, scope) {
        Err(err)
      } else {
        Ok(Call {
          procedure: call.procedure,
          payload: intermediate.payload,
        })
      }
    } else {
      call
    }
  }

  fn unwrap_reply(&self, reply: Result<crate::Reply<<Self::Backend as Backend>::Intermediate>>) -> Result<crate::Reply<<Self::Backend as Backend>::Intermediate>> {
    reply
  }

  fn as_any(&mut self) -> &mut dyn core::any::Any {
    self
  }
}