Skip to main content

aauth_person_server/
types.rs

1use aauth_protocol::{Budget, Jwk};
2use serde::{Deserialize, Serialize};
3use serde_json::{Map, Value};
4
5/// Someone this Person Server speaks for.
6#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
7pub struct Person {
8    /// Stable within this Person Server; never shown to a resource, which
9    /// gets a directed `sub` instead.
10    pub id: String,
11    /// A display name.
12    #[serde(default, skip_serializing_if = "Option::is_none")]
13    pub name: Option<String>,
14    /// An email address.
15    #[serde(default, skip_serializing_if = "Option::is_none")]
16    pub email: Option<String>,
17    /// The person's organisation, per OpenID Connect Enterprise; carried as `tenant`.
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub tenant: Option<String>,
20    /// Anything else the directory knows, kept with the person.
21    #[serde(flatten)]
22    pub extra: Map<String, Value>,
23}
24
25impl Person {
26    /// A person known only by `id`.
27    pub fn new(id: impl Into<String>) -> Self {
28        Self {
29            id: id.into(),
30            ..Default::default()
31        }
32    }
33}
34
35/// The agent a request came from, once its signature and agent token verify.
36#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
37pub struct Agent {
38    /// `aauth:local@domain`.
39    pub id: String,
40    /// Its agent provider.
41    pub provider: String,
42    /// JWK thumbprint of its signing key.
43    pub jkt: String,
44    /// Its signing key.
45    pub jwk: Jwk,
46    /// When its agent token expires, in seconds since the epoch.
47    pub exp: i64,
48}
49
50/// What an approval decides.
51#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(rename_all = "snake_case")]
53#[non_exhaustive]
54pub enum ApprovalKind {
55    /// A mission the agent proposed.
56    Mission,
57    /// An auth token the mission's budget does not cover.
58    AuthToken,
59    /// The agent says the mission is done; the person accepts.
60    Completion,
61}
62
63/// Where an approval stands.
64#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
65#[serde(rename_all = "snake_case")]
66#[non_exhaustive]
67pub enum ApprovalStatus {
68    /// Nobody has decided.
69    Pending,
70    /// Approved. A mission's tokens are minted on each poll while it is
71    /// live; an auth token is minted on the first, which collects it.
72    Approved,
73    /// Denied, by the approver or withdrawn by the agent.
74    Denied,
75    /// Approved, and its auth token handed to the agent: an auth token is
76    /// collected once, so one approval spends once.
77    Collected,
78}
79
80/// A decision this Person Server waits on. Whoever operates it makes the decision.
81#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
82pub struct Approval {
83    /// Its identifier, in the pending URL.
84    pub id: String,
85    /// What it decides.
86    pub kind: ApprovalKind,
87    /// Where it stands.
88    pub status: ApprovalStatus,
89    /// The agent that asked.
90    pub agent: Agent,
91    /// Who the agent acts for.
92    pub person: Person,
93    /// Who should decide: the directory's answer, else the person.
94    pub approver: Person,
95    /// The mission's description, the request's scope, or the completion summary.
96    pub description: String,
97    /// The resource's own one-line description of an auth token request (its R3 proposal).
98    #[serde(default, skip_serializing_if = "Option::is_none")]
99    pub summary: Option<String>,
100    /// The mission it is under, or the one approving it created.
101    #[serde(default, skip_serializing_if = "Option::is_none")]
102    pub mission_s256: Option<String>,
103    /// The resource an auth token request is for.
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    pub resource: Option<String>,
106    /// What an auth token request costs, from the resource token.
107    #[serde(default, skip_serializing_if = "Option::is_none")]
108    pub budget: Option<Budget>,
109    /// For a mission: the resources it would cover. For an auth token: the claims it is minted with.
110    #[serde(default)]
111    pub grant: Map<String, Value>,
112    /// Approving a mission sets its budget.
113    #[serde(default, skip_serializing_if = "Option::is_none")]
114    pub decided_budget: Option<Budget>,
115    /// Who decided, in the operator's terms.
116    #[serde(default, skip_serializing_if = "Option::is_none")]
117    pub decided_by: Option<String>,
118    /// When it was decided, in seconds since the epoch.
119    #[serde(default, skip_serializing_if = "Option::is_none")]
120    pub decided_at: Option<i64>,
121    /// When it was asked, in seconds since the epoch.
122    pub created_at: i64,
123    /// When it expires undecided.
124    pub expires_at: i64,
125    /// The tenant it was asked under.
126    pub tenant: String,
127}
128
129/// An update the agent logged against a mission.
130#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
131pub struct LogEntry {
132    /// When, in seconds since the epoch.
133    pub at: i64,
134    /// What the agent said.
135    pub entry: String,
136}
137
138/// An approved mission.
139#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
140pub struct Mission {
141    /// Its hash: the `mission_s256` its tokens carry.
142    pub s256: String,
143    /// The approved mission's bytes, as hashed and returned to the agent.
144    pub mission: String,
145    /// The agent it was approved for.
146    pub agent_id: String,
147    /// The person it acts for.
148    pub person: Person,
149    /// The resources it covers.
150    pub resources: Vec<String>,
151    /// What it may spend, when the approver set a budget.
152    #[serde(default, skip_serializing_if = "Option::is_none")]
153    pub budget: Option<Budget>,
154    /// What it has spent, in the budget's unit and scale.
155    pub spent: u64,
156    /// The agent's updates.
157    #[serde(default)]
158    pub log: Vec<LogEntry>,
159    /// When it ends, in seconds since the epoch.
160    pub expires_at: i64,
161    /// When it was completed or terminated.
162    #[serde(default, skip_serializing_if = "Option::is_none")]
163    pub completed_at: Option<i64>,
164    /// The tenant it was approved under.
165    pub tenant: String,
166}
167
168impl Mission {
169    /// Not completed, and not past its expiry at `now`.
170    pub fn is_live(&self, now: i64) -> bool {
171        self.completed_at.is_none() && now < self.expires_at
172    }
173}
174
175/// An approver's answer.
176#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
177#[serde(rename_all = "snake_case")]
178pub enum Verdict {
179    /// Do what was asked.
180    Approved,
181    /// Refuse it.
182    Denied,
183}
184
185/// What an operator answers.
186#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
187pub struct Decision {
188    /// Approve or deny.
189    pub status: Verdict,
190    /// A mission's budget, when approving one.
191    #[serde(default, skip_serializing_if = "Option::is_none")]
192    pub budget: Option<Budget>,
193    /// Who decided, in the operator's own terms.
194    #[serde(default, skip_serializing_if = "Option::is_none")]
195    pub decided_by: Option<String>,
196}
197
198impl Decision {
199    /// Approve.
200    pub fn approve() -> Self {
201        Self {
202            status: Verdict::Approved,
203            budget: None,
204            decided_by: None,
205        }
206    }
207
208    /// Deny.
209    pub fn deny() -> Self {
210        Self {
211            status: Verdict::Denied,
212            budget: None,
213            decided_by: None,
214        }
215    }
216
217    /// Set the budget an approved mission may spend.
218    pub fn with_budget(mut self, budget: Budget) -> Self {
219        self.budget = Some(budget);
220        self
221    }
222
223    /// Record who decided.
224    pub fn by(mut self, decided_by: impl Into<String>) -> Self {
225        self.decided_by = Some(decided_by.into());
226        self
227    }
228}
229
230/// Which token was issued.
231#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
232#[serde(rename_all = "snake_case")]
233#[non_exhaustive]
234pub enum IssuedKind {
235    /// A person token.
236    PersonToken,
237    /// An auth token.
238    AuthToken,
239}
240
241/// A token this Person Server issued, as [`crate::Hooks::on_issued`] reports it.
242#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
243pub struct Issued {
244    /// Which token.
245    pub kind: IssuedKind,
246    /// The agent it was issued to.
247    pub agent: Agent,
248    /// Who it speaks for.
249    pub person: Person,
250    /// The resource it is for.
251    pub resource: String,
252    /// The mission it was issued under.
253    #[serde(default, skip_serializing_if = "Option::is_none")]
254    pub mission: Option<Mission>,
255    /// What it spent from the mission's budget.
256    #[serde(default, skip_serializing_if = "Option::is_none")]
257    pub budget: Option<Budget>,
258}