authnz-common 0.2.1

Authnz common library (types and utils).
Documentation
//! Authorization module.

#[cfg(any(feature = "app-server-types", feature = "authnz-server-types"))]
use impulse_server_kit::salvo;
#[cfg(any(feature = "app-server-types", feature = "authnz-server-types"))]
use salvo::oapi::ToSchema;
use serde::{Deserialize, Serialize};

use crate::{AccessTag, CBAChallengeSign, Id, TokenBundle};
use crate::{MResult, ServerError};

#[cfg_attr(any(feature = "app-server-types", feature = "authnz-server-types"), derive(ToSchema))]
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug)]
/// Request to authorize.
///
/// Application clients can use this request both for check logged in
/// and for check if user has sufficient rights to a resource (by requested tags).
pub struct UserAuthorizeRequest {
  /// Access token (MPAAT).
  pub access_token: String,
  /// Refresh token (random bytes).
  pub refresh_token: String,
  /// Client token (MPAAT).
  pub client_token: Option<String>,
  /// May contains client-based authorization challenge sign.
  ///
  /// If CBA is required, and CBA token is invalid, and the challenge state is provided,
  /// and the challenge sign is valid and made by known client key, C3A will deploy new client token.
  pub cba_challenge_sign: Option<CBAChallengeSign>,
  /// Requested tags to authorize resource usage.
  pub requested_tags: Vec<AccessTag>,
}

impl UserAuthorizeRequest {
  /// Creates a new authorization request builder.
  #[allow(clippy::new_ret_no_self)]
  pub fn new() -> UserAuthorizeRequestBuilder {
    Default::default()
  }

  /// Splits request into parts.
  pub fn into_parts(self) -> (TokenBundle, Vec<AccessTag>, Option<CBAChallengeSign>) {
    (
      TokenBundle {
        access: self.access_token,
        refresh: self.refresh_token,
        client: self.client_token,
      },
      self.requested_tags,
      self.cba_challenge_sign,
    )
  }
}

#[derive(Default)]
/// Request builder.
pub struct UserAuthorizeRequestBuilder {
  access_token: Option<String>,
  refresh_token: Option<String>,
  client_token: Option<String>,
  cba_challenge_sign: Option<CBAChallengeSign>,
  requested_tags: Option<Vec<AccessTag>>,
}

impl UserAuthorizeRequestBuilder {
  /// Specifies simple token pair (access & refresh tokens).
  pub fn token_pair(mut self, act: impl ToString, rft: impl ToString) -> Self {
    self.access_token = Some(act.to_string());
    self.refresh_token = Some(rft.to_string());
    self
  }

  /// Specifies token triple (access, refresh & client tokens).
  pub fn token_triple(mut self, act: impl ToString, rft: impl ToString, cba: impl ToString) -> Self {
    self.access_token = Some(act.to_string());
    self.refresh_token = Some(rft.to_string());
    self.client_token = Some(cba.to_string());
    self
  }

  /// Specifies requested tags.
  pub fn with_tags(mut self, tags: &[impl Into<AccessTag> + Clone]) -> Self {
    let tags = tags.iter().map(|t| t.clone().into()).collect::<Vec<_>>();
    self.requested_tags = Some(tags);
    self
  }

  /// Specifies no tags (just to authenticate).
  pub fn no_tags(mut self) -> Self {
    self.requested_tags = Some(vec![]);
    self
  }

  /// Specifies signed challenge.
  pub fn with_signed_challenge(mut self, sign: Vec<u8>) -> Self {
    self.cba_challenge_sign = Some(CBAChallengeSign::new(sign));
    self
  }

  /// Builds the request.
  #[allow(clippy::unwrap_used)]
  pub fn build(self) -> MResult<UserAuthorizeRequest> {
    if self.access_token.is_none() || self.refresh_token.is_none() {
      ServerError::from_private_str("You must specify at least access & refresh tokens to perform authorization request!")
        .with_500()
        .bail()?;
    }

    if self.requested_tags.is_none() {
      ServerError::from_private_str("You must specify requested tags (`.with_tags(...)`) or no tags at all (`.no_tags()`) explicitly!")
        .with_500()
        .bail()?;
    }

    let req = UserAuthorizeRequest {
      access_token: unsafe { self.access_token.unwrap_unchecked() },
      refresh_token: unsafe { self.refresh_token.unwrap_unchecked() },
      client_token: self.client_token,
      requested_tags: unsafe { self.requested_tags.unwrap_unchecked() },
      cba_challenge_sign: self.cba_challenge_sign,
    };

    Ok(req)
  }
}

#[cfg_attr(any(feature = "app-server-types", feature = "authnz-server-types"), derive(ToSchema))]
#[derive(Deserialize, Serialize, PartialEq, Eq, Hash, Clone, Debug)]
/// Authorize response.
pub struct UserAuthorizeResponse {
  /// Grants permissions to the resource.
  pub approved: bool,
  /// Identifies the user.
  pub id: Option<Id>,
  /// May contains new access token.
  ///
  /// Application backend must update it in the application client.
  pub new_access_token: Option<String>,
  /// May contains new client-based authorization challenge.
  ///
  /// Application backend must transfer this challenge to the application client
  /// and repeat request after getting `cba_challenge_sign` from it.
  /// Note that application backend also must transfer challenge state from the headers!
  pub new_cba_challenge: Option<Vec<u8>>,
  /// May contains new client token.
  ///
  /// Application backend must update it in the application client.
  pub new_cba_token: Option<String>,
}

#[derive(Deserialize, Serialize)]
/// Simple response with permission grant.
pub struct ApplicationAuthorizeResponse {
  /// Authorization status.
  pub authorized: bool,
  /// Authorized user's ID.
  pub user_id: Option<Id>,
}