1use super::mandate_store::{AuthzError, AuthzReceipt, MandateStore};
13use chrono::{DateTime, Utc};
14use thiserror::Error;
15
16#[path = "authorizer_internal/mod.rs"]
17mod authorizer_internal;
18
19pub const DEFAULT_CLOCK_SKEW_SECONDS: i64 = 30;
21
22#[derive(Debug, Clone)]
24pub struct AuthzConfig {
25 pub clock_skew_seconds: i64,
27 pub expected_audience: String,
29 pub trusted_issuers: Vec<String>,
31}
32
33impl Default for AuthzConfig {
34 fn default() -> Self {
35 Self {
36 clock_skew_seconds: DEFAULT_CLOCK_SKEW_SECONDS,
37 expected_audience: String::new(),
38 trusted_issuers: Vec::new(),
39 }
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
45pub enum OperationClass {
46 Read = 0,
47 Write = 1,
48 Commit = 2,
49}
50
51impl OperationClass {
52 pub fn as_str(&self) -> &'static str {
53 match self {
54 Self::Read => "read",
55 Self::Write => "write",
56 Self::Commit => "commit",
57 }
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum MandateKind {
64 Intent,
65 Transaction,
66}
67
68impl MandateKind {
69 pub fn as_str(&self) -> &'static str {
70 match self {
71 Self::Intent => "intent",
72 Self::Transaction => "transaction",
73 }
74 }
75
76 pub fn max_operation_class(&self) -> OperationClass {
78 match self {
79 Self::Intent => OperationClass::Write, Self::Transaction => OperationClass::Commit, }
82 }
83}
84
85#[derive(Debug, Clone)]
87pub struct MandateData {
88 pub mandate_id: String,
89 pub mandate_kind: MandateKind,
90 pub audience: String,
91 pub issuer: String,
92 pub tool_patterns: Vec<String>,
93 pub operation_class: Option<OperationClass>,
94 pub transaction_ref: Option<String>,
95 pub not_before: Option<DateTime<Utc>>,
96 pub expires_at: Option<DateTime<Utc>>,
97 pub single_use: bool,
98 pub max_uses: Option<u32>,
99 pub nonce: Option<String>,
100 pub canonical_digest: String,
101 pub key_id: String,
102}
103
104#[derive(Debug, Clone)]
106pub struct ToolCallData {
107 pub tool_call_id: String,
108 pub tool_name: String,
109 pub operation_class: OperationClass,
110 pub transaction_object: Option<serde_json::Value>,
111 pub source_run_id: Option<String>,
112}
113
114#[derive(Debug, Error, PartialEq, Eq)]
116pub enum PolicyError {
117 #[error("Mandate expired: expires_at={expires_at}, now={now}")]
118 Expired {
119 expires_at: DateTime<Utc>,
120 now: DateTime<Utc>,
121 },
122
123 #[error("Mandate not yet valid: not_before={not_before}, now={now}")]
124 NotYetValid {
125 not_before: DateTime<Utc>,
126 now: DateTime<Utc>,
127 },
128
129 #[error("Tool '{tool}' not in mandate scope")]
130 ToolNotInScope { tool: String },
131
132 #[error("Mandate kind '{kind}' does not allow operation class '{op_class}'")]
133 KindMismatch { kind: String, op_class: String },
134
135 #[error("Audience mismatch: expected '{expected}', got '{actual}'")]
136 AudienceMismatch { expected: String, actual: String },
137
138 #[error("Issuer '{issuer}' not in trusted issuers")]
139 IssuerNotTrusted { issuer: String },
140
141 #[error("Missing transaction object for commit tool")]
142 MissingTransactionObject,
143
144 #[error("Transaction ref mismatch: expected '{expected}', got '{actual}'")]
145 TransactionRefMismatch { expected: String, actual: String },
146}
147
148#[derive(Debug, Error)]
150pub enum AuthorizeError {
151 #[error("Policy error: {0}")]
152 Policy(#[from] PolicyError),
153
154 #[error("Store error: {0}")]
155 Store(#[from] AuthzError),
156
157 #[error("Failed to compute transaction ref: {0}")]
158 TransactionRef(String),
159}
160
161pub struct Authorizer {
163 store: MandateStore,
164 config: AuthzConfig,
165}
166
167impl Authorizer {
168 pub fn new(store: MandateStore, config: AuthzConfig) -> Self {
170 Self { store, config }
171 }
172
173 pub fn authorize_and_consume(
184 &self,
185 mandate: &MandateData,
186 tool_call: &ToolCallData,
187 ) -> Result<AuthzReceipt, AuthorizeError> {
188 authorizer_internal::run::authorize_and_consume_impl(self, mandate, tool_call)
189 }
190
191 pub fn authorize_at(
194 &self,
195 now: DateTime<Utc>,
196 mandate: &MandateData,
197 tool_call: &ToolCallData,
198 ) -> Result<AuthzReceipt, AuthorizeError> {
199 authorizer_internal::run::authorize_at_impl(self, now, mandate, tool_call)
200 }
201}