Skip to main content

open_payments/types/
auth.rs

1use crate::types::common::{Amount, Interval, Receiver};
2use crate::types::wallet_address::JsonWebKey;
3use serde::{Deserialize, Serialize};
4
5/// Client identification for grant requests.
6///
7/// Open Payments accepts either a wallet address string (backwards compatible),
8/// a `{ "walletAddress": "..." }` object, or a directed-identity `{ "jwk": ... }` object.
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10#[serde(untagged)]
11pub enum Client {
12    /// Deprecated string form of the client wallet address.
13    WalletAddressUrl(String),
14    /// Object form with a wallet address.
15    WalletAddress {
16        #[serde(rename = "walletAddress")]
17        wallet_address: String,
18    },
19    /// Directed identity — public key embedded in the grant request.
20    Jwk { jwk: JsonWebKey },
21}
22
23/// Subject information requested or returned in a grant.
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
25pub struct Subject {
26    pub sub_ids: Vec<SubjectIdentifier>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
30pub struct SubjectIdentifier {
31    pub id: String,
32    pub format: SubjectIdentifierFormat,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
36#[serde(rename_all = "lowercase")]
37pub enum SubjectIdentifierFormat {
38    Uri,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
42#[serde(tag = "type", rename_all = "kebab-case")]
43pub enum AccessItem {
44    #[serde(rename = "incoming-payment")]
45    IncomingPayment {
46        actions: Vec<IncomingPaymentAction>,
47        #[serde(skip_serializing_if = "Option::is_none")]
48        identifier: Option<String>,
49    },
50    #[serde(rename = "outgoing-payment")]
51    OutgoingPayment {
52        actions: Vec<OutgoingPaymentAction>,
53        identifier: String,
54        #[serde(skip_serializing_if = "Option::is_none")]
55        limits: Option<LimitsOutgoing>,
56    },
57    #[serde(rename = "quote")]
58    Quote { actions: Vec<QuoteAction> },
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
62#[serde(rename_all = "kebab-case")]
63pub enum IncomingPaymentAction {
64    Create,
65    Complete,
66    Read,
67    ReadAll,
68    List,
69    ListAll,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
73#[serde(rename_all = "kebab-case")]
74pub enum OutgoingPaymentAction {
75    Create,
76    Read,
77    ReadAll,
78    List,
79    ListAll,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
83#[serde(rename_all = "kebab-case")]
84pub enum QuoteAction {
85    Create,
86    Read,
87    ReadAll,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
91#[serde(rename_all = "camelCase")]
92pub struct LimitsOutgoing {
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub receiver: Option<Receiver>,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub debit_amount: Option<Amount>,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub receive_amount: Option<Amount>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub interval: Option<Interval>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
104pub struct AccessToken {
105    pub value: String,
106    pub manage: String,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub expires_in: Option<i64>,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub access: Option<Vec<AccessItem>>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
114pub struct AccessTokenResponse {
115    pub access_token: AccessToken,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
119pub struct GrantRequest {
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub access_token: Option<AccessTokenRequest>,
122    pub(crate) client: Client,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub interact: Option<InteractRequest>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub subject: Option<Subject>,
127}
128
129impl GrantRequest {
130    /// Creates a grant request with the given access token request.
131    ///
132    /// The `client` field is filled by [`AuthenticatedResources::grant`] when the
133    /// request is sent. Optionally attach [`Subject`] via [`GrantRequest::with_subject`].
134    pub fn new(access_token: AccessTokenRequest, interact: Option<InteractRequest>) -> Self {
135        Self {
136            access_token: Some(access_token),
137            client: Client::WalletAddressUrl(String::new()),
138            interact,
139            subject: None,
140        }
141    }
142
143    /// Creates a grant request that only asks for subject information.
144    pub fn subject_only(subject: Subject, interact: Option<InteractRequest>) -> Self {
145        Self {
146            access_token: None,
147            client: Client::WalletAddressUrl(String::new()),
148            interact,
149            subject: Some(subject),
150        }
151    }
152
153    /// Attaches subject information to this grant request.
154    pub fn with_subject(mut self, subject: Subject) -> Self {
155        self.subject = Some(subject);
156        self
157    }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
161pub struct AccessTokenRequest {
162    pub access: Vec<AccessItem>,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
166pub struct InteractRequest {
167    pub start: Vec<String>,
168    pub finish: Option<InteractFinish>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
172pub struct InteractFinish {
173    pub method: String,
174    pub uri: String,
175    pub nonce: String,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
179pub struct InteractResponse {
180    pub redirect: String,
181    pub finish: String,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
185pub struct Continue {
186    pub access_token: ContinueAccessToken,
187    pub uri: String,
188    pub wait: Option<i64>,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
192pub struct ContinueAccessToken {
193    pub value: String,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
197#[serde(untagged)]
198pub enum GrantResponse {
199    WithInteraction {
200        interact: InteractResponse,
201        #[serde(rename = "continue")]
202        continue_: Continue,
203    },
204    WithToken {
205        access_token: AccessToken,
206        #[serde(rename = "continue")]
207        continue_: Continue,
208        #[serde(default, skip_serializing_if = "Option::is_none")]
209        subject: Option<Subject>,
210    },
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
214pub struct ContinueRequest {
215    pub interact_ref: Option<String>,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
219#[serde(untagged)]
220pub enum ContinueResponse {
221    WithToken {
222        access_token: AccessToken,
223        #[serde(rename = "continue")]
224        continue_: Continue,
225        #[serde(default, skip_serializing_if = "Option::is_none")]
226        subject: Option<Subject>,
227    },
228    WithSubject {
229        subject: Subject,
230        #[serde(rename = "continue")]
231        continue_: Continue,
232    },
233    Pending {
234        #[serde(rename = "continue")]
235        continue_: Continue,
236    },
237}
238
239impl ContinueResponse {
240    pub fn has_access_token(&self) -> bool {
241        matches!(self, Self::WithToken { .. })
242    }
243
244    pub fn has_subject(&self) -> bool {
245        match self {
246            Self::WithSubject { .. } => true,
247            Self::WithToken { subject, .. } => subject.is_some(),
248            Self::Pending { .. } => false,
249        }
250    }
251}