Skip to main content

auths_sdk/workflows/
approval.rs

1//! Approval workflow functions.
2//!
3//! Three-phase design:
4//! 1. `build_approval_attestation` — pure, deterministic attestation construction.
5//! 2. `apply_approval` — side-effecting: consume nonce, remove pending request.
6//! 3. `grant_approval` — high-level orchestrator (calls load → build → apply).
7
8use chrono::{DateTime, Duration, Utc};
9
10use auths_policy::approval::ApprovalAttestation;
11use auths_policy::types::{CanonicalCapability, CanonicalDid};
12
13use crate::error::ApprovalError;
14
15/// Config for granting an approval.
16pub struct GrantApprovalConfig {
17    /// Hex-encoded hash of the pending request.
18    pub request_hash: String,
19    /// DID of the approver.
20    pub approver_did: String,
21    /// Optional note for the approval.
22    pub note: Option<String>,
23}
24
25/// Config for listing pending approvals.
26pub struct ListApprovalsConfig {
27    /// Path to the repository.
28    pub repo_path: std::path::PathBuf,
29}
30
31/// Result of granting an approval.
32pub struct GrantApprovalResult {
33    /// The request hash that was approved.
34    pub request_hash: String,
35    /// DID of the approver.
36    pub approver_did: String,
37    /// The unique JTI for this approval.
38    pub jti: String,
39    /// When the approval expires.
40    pub expires_at: DateTime<Utc>,
41    /// Human-readable summary of what was approved.
42    pub context_summary: String,
43}
44
45/// Build an approval attestation from a pending request (pure function).
46///
47/// Args:
48/// * `request_hash_hex`: Hex-encoded request hash.
49/// * `approver_did`: DID of the human approver.
50/// * `capabilities`: Capabilities being approved.
51/// * `now`: Current time.
52/// * `expires_at`: When the approval expires.
53///
54/// Usage:
55/// ```ignore
56/// let attestation = build_approval_attestation("abc123", &did, &caps, now, expires)?;
57/// ```
58pub fn build_approval_attestation(
59    request_hash_hex: &str,
60    approver_did: CanonicalDid,
61    capabilities: Vec<CanonicalCapability>,
62    now: DateTime<Utc>,
63    expires_at: DateTime<Utc>,
64) -> Result<ApprovalAttestation, ApprovalError> {
65    if now >= expires_at {
66        return Err(ApprovalError::RequestExpired { expires_at });
67    }
68
69    let request_hash = auths_verifier::Hash256::new(hex_to_hash(request_hash_hex)?);
70    let jti = uuid_v4(now);
71
72    // Cap the attestation expiry to 5 minutes from now
73    let attestation_expires = std::cmp::min(expires_at, now + Duration::minutes(5));
74
75    Ok(ApprovalAttestation {
76        jti,
77        approver_did,
78        request_hash,
79        expires_at: attestation_expires,
80        approved_capabilities: capabilities,
81    })
82}
83
84fn hex_to_hash(hex: &str) -> Result<[u8; 32], ApprovalError> {
85    let bytes = hex::decode(hex).map_err(|_| ApprovalError::RequestNotFound {
86        hash: hex.to_string(),
87    })?;
88    if bytes.len() != 32 {
89        return Err(ApprovalError::RequestNotFound {
90            hash: hex.to_string(),
91        });
92    }
93    let mut arr = [0u8; 32];
94    arr.copy_from_slice(&bytes);
95    Ok(arr)
96}
97
98fn uuid_v4(now: DateTime<Utc>) -> String {
99    let ts = now.timestamp_nanos_opt().unwrap_or_default() as u64;
100    format!(
101        "{:08x}-{:04x}-4{:03x}-{:04x}-{:012x}",
102        (ts >> 32) as u32,
103        (ts >> 16) & 0xffff,
104        ts & 0x0fff,
105        0x8000 | ((ts >> 20) & 0x3fff),
106        ts & 0xffffffffffff,
107    )
108}