1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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
  }
}