aauth 0.0.2

Rust implementation of the AAuth authorization protocol
Documentation
use serde::{Deserialize, Serialize};

use crate::jwt::{AgentClaims, ResourceClaims};
use crate::types::{AAuthProtocolError, TokenExchangeRequest, TokenResponseBody};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DeferRequirement {
    Interaction { url: String, code: String },
    Clarification { question: String, timeout: Option<u64> },
    Claims { required_claims: Vec<String> },
    Approval,
    Payment { location: String },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PendingStatus {
    Pending,
    Interacting,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PendingOutcome {
    AuthToken(TokenResponseBody),
    OpaqueAccess(String),
    Error(AAuthProtocolError),
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PendingSnapshot {
    pub status: PendingStatus,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub requirement: Option<DeferRequirement>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub outcome: Option<PendingOutcome>,
}

impl PendingSnapshot {
    pub fn waiting(requirement: DeferRequirement) -> Self {
        Self {
            status: PendingStatus::Pending,
            requirement: Some(requirement),
            outcome: None,
        }
    }

    pub fn complete(outcome: PendingOutcome) -> Self {
        Self {
            status: PendingStatus::Pending,
            requirement: None,
            outcome: Some(outcome),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ClaimsSubmission {
    pub sub: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tenant: Option<String>,
    #[serde(flatten)]
    pub extra: std::collections::HashMap<String, serde_json::Value>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PendingInput {
    ClarificationResponse(String),
    ClaimsSubmission(ClaimsSubmission),
    InteractionCompleted,
    Cancelled,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PendingKind {
    PersonToken,
    AccessToken,
    ResourceAccess,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FederationPendingState {
    pub access_server_url: String,
    pub as_pending_url: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PersonPendingContext {
    pub person_server_url: String,
    pub resource_url: String,
    pub agent_claims: AgentClaims,
    pub resource_claims: ResourceClaims,
    pub exchange_request: TokenExchangeRequest,
    pub agent_token: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub federation: Option<FederationPendingState>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AccessPendingContext {
    pub access_server_url: String,
    pub resource_url: String,
    pub person_server_url: String,
    pub agent_claims: AgentClaims,
    pub resource_claims: ResourceClaims,
    pub resource_token: String,
    pub agent_token: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResourcePendingContext {
    pub resource_url: String,
    pub agent_claims: AgentClaims,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scope: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PendingContext {
    Person(PersonPendingContext),
    Access(AccessPendingContext),
    Resource(ResourcePendingContext),
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PendingRecord {
    pub id: String,
    pub created_at: u64,
    pub expires_at: u64,
    pub kind: PendingKind,
    pub context: PendingContext,
    pub snapshot: PendingSnapshot,
}

impl PendingRecord {
    pub fn new(
        id: String,
        kind: PendingKind,
        context: PendingContext,
        snapshot: PendingSnapshot,
        ttl_secs: u64,
    ) -> Self {
        let created_at = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs();
        Self {
            id,
            created_at,
            expires_at: created_at + ttl_secs,
            kind,
            context,
            snapshot,
        }
    }

    pub fn is_expired(&self) -> bool {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs();
        now > self.expires_at
    }
}