Skip to main content

adk_payments/auth/
scopes.rs

1use std::fmt;
2
3use adk_auth::{ScopeDenied, check_scopes};
4use serde::{Deserialize, Serialize};
5
6use super::PaymentsAuthError;
7
8/// Scope for checkout creation.
9pub const PAYMENT_CHECKOUT_CREATE_SCOPE: &str = "payments:checkout:create";
10
11/// Scope for checkout updates.
12pub const PAYMENT_CHECKOUT_UPDATE_SCOPE: &str = "payments:checkout:update";
13
14/// Scope for checkout completion.
15pub const PAYMENT_CHECKOUT_COMPLETE_SCOPE: &str = "payments:checkout:complete";
16
17/// Scope for checkout cancelation.
18pub const PAYMENT_CHECKOUT_CANCEL_SCOPE: &str = "payments:checkout:cancel";
19
20/// Scope for delegated credential use.
21pub const PAYMENT_CREDENTIAL_DELEGATE_SCOPE: &str = "payments:credential:delegate";
22
23/// Scope for payment intervention continuation.
24pub const PAYMENT_INTERVENTION_CONTINUE_SCOPE: &str = "payments:intervention:continue";
25
26/// Scope for order mutation after checkout.
27pub const PAYMENT_ORDER_UPDATE_SCOPE: &str = "payments:order:update";
28
29/// Scope for administrative payment operations.
30pub const PAYMENT_ADMIN_SCOPE: &str = "payments:admin";
31
32/// Scope for settlement-specific operations.
33pub const PAYMENT_SETTLEMENT_SCOPE: &str = "payments:settlement:write";
34
35/// Scope set for checkout creation.
36pub const CHECKOUT_CREATE_SCOPES: &[&str] = &[PAYMENT_CHECKOUT_CREATE_SCOPE];
37
38/// Scope set for checkout updates.
39pub const CHECKOUT_UPDATE_SCOPES: &[&str] = &[PAYMENT_CHECKOUT_UPDATE_SCOPE];
40
41/// Scope set for checkout completion.
42pub const CHECKOUT_COMPLETE_SCOPES: &[&str] = &[PAYMENT_CHECKOUT_COMPLETE_SCOPE];
43
44/// Scope set for checkout cancelation.
45pub const CHECKOUT_CANCEL_SCOPES: &[&str] = &[PAYMENT_CHECKOUT_CANCEL_SCOPE];
46
47/// Scope set for delegated credential usage.
48pub const CREDENTIAL_DELEGATE_SCOPES: &[&str] = &[PAYMENT_CREDENTIAL_DELEGATE_SCOPE];
49
50/// Scope set for intervention continuation.
51pub const INTERVENTION_CONTINUE_SCOPES: &[&str] = &[PAYMENT_INTERVENTION_CONTINUE_SCOPE];
52
53/// Scope set for order updates.
54pub const ORDER_UPDATE_SCOPES: &[&str] = &[PAYMENT_ORDER_UPDATE_SCOPE];
55
56/// Scope set for administrative operations.
57pub const ADMIN_SCOPES: &[&str] = &[PAYMENT_ADMIN_SCOPE];
58
59/// Scope set for settlement operations.
60pub const SETTLEMENT_SCOPES: &[&str] = &[PAYMENT_SETTLEMENT_SCOPE];
61
62/// Catalog of all currently defined payment scopes.
63pub const ALL_PAYMENT_SCOPES: &[&str] = &[
64    PAYMENT_CHECKOUT_CREATE_SCOPE,
65    PAYMENT_CHECKOUT_UPDATE_SCOPE,
66    PAYMENT_CHECKOUT_COMPLETE_SCOPE,
67    PAYMENT_CHECKOUT_CANCEL_SCOPE,
68    PAYMENT_CREDENTIAL_DELEGATE_SCOPE,
69    PAYMENT_INTERVENTION_CONTINUE_SCOPE,
70    PAYMENT_ORDER_UPDATE_SCOPE,
71    PAYMENT_ADMIN_SCOPE,
72    PAYMENT_SETTLEMENT_SCOPE,
73];
74
75/// Payment-sensitive operations that map to named scopes and audit resources.
76///
77/// # Example
78///
79/// ```
80/// use adk_payments::auth::{PaymentOperation, check_payment_operation_scopes};
81///
82/// let granted = vec!["payments:checkout:create".to_string()];
83/// check_payment_operation_scopes(PaymentOperation::CreateCheckout, &granted).unwrap();
84/// ```
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
86#[serde(rename_all = "snake_case")]
87pub enum PaymentOperation {
88    CreateCheckout,
89    UpdateCheckout,
90    CompleteCheckout,
91    CancelCheckout,
92    DelegateCredential,
93    ContinueIntervention,
94    UpdateOrder,
95    Settlement,
96    Admin,
97}
98
99impl PaymentOperation {
100    /// Returns the stable operation identifier used in audit metadata.
101    #[must_use]
102    pub const fn as_str(self) -> &'static str {
103        match self {
104            Self::CreateCheckout => "checkout_create",
105            Self::UpdateCheckout => "checkout_update",
106            Self::CompleteCheckout => "checkout_complete",
107            Self::CancelCheckout => "checkout_cancel",
108            Self::DelegateCredential => "credential_delegate",
109            Self::ContinueIntervention => "intervention_continue",
110            Self::UpdateOrder => "order_update",
111            Self::Settlement => "settlement",
112            Self::Admin => "admin",
113        }
114    }
115
116    /// Returns the audit resource key associated with the operation.
117    #[must_use]
118    pub const fn audit_resource(self) -> &'static str {
119        match self {
120            Self::CreateCheckout => "payments.checkout.create",
121            Self::UpdateCheckout => "payments.checkout.update",
122            Self::CompleteCheckout => "payments.checkout.complete",
123            Self::CancelCheckout => "payments.checkout.cancel",
124            Self::DelegateCredential => "payments.credential.delegate",
125            Self::ContinueIntervention => "payments.intervention.continue",
126            Self::UpdateOrder => "payments.order.update",
127            Self::Settlement => "payments.settlement.write",
128            Self::Admin => "payments.admin",
129        }
130    }
131
132    /// Returns the scopes required to invoke the operation.
133    #[must_use]
134    pub const fn required_scopes(self) -> &'static [&'static str] {
135        match self {
136            Self::CreateCheckout => CHECKOUT_CREATE_SCOPES,
137            Self::UpdateCheckout => CHECKOUT_UPDATE_SCOPES,
138            Self::CompleteCheckout => CHECKOUT_COMPLETE_SCOPES,
139            Self::CancelCheckout => CHECKOUT_CANCEL_SCOPES,
140            Self::DelegateCredential => CREDENTIAL_DELEGATE_SCOPES,
141            Self::ContinueIntervention => INTERVENTION_CONTINUE_SCOPES,
142            Self::UpdateOrder => ORDER_UPDATE_SCOPES,
143            Self::Settlement => SETTLEMENT_SCOPES,
144            Self::Admin => ADMIN_SCOPES,
145        }
146    }
147}
148
149impl fmt::Display for PaymentOperation {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        f.write_str(self.as_str())
152    }
153}
154
155/// Checks that the granted scopes authorize one payment operation.
156///
157/// # Errors
158///
159/// Returns [`PaymentsAuthError::MissingScopes`] when the granted scopes do not
160/// satisfy the operation requirements.
161pub fn check_payment_operation_scopes(
162    operation: PaymentOperation,
163    granted: &[String],
164) -> Result<(), PaymentsAuthError> {
165    check_scopes(operation.required_scopes(), granted)
166        .map_err(|denied| scope_denied(operation, denied))
167}
168
169fn scope_denied(operation: PaymentOperation, denied: ScopeDenied) -> PaymentsAuthError {
170    PaymentsAuthError::MissingScopes {
171        operation,
172        required: denied.required,
173        missing: denied.missing,
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn scope_check_rejects_missing_scope() {
183        let granted = vec![PAYMENT_CHECKOUT_CREATE_SCOPE.to_string()];
184        let err = check_payment_operation_scopes(PaymentOperation::CompleteCheckout, &granted)
185            .unwrap_err();
186
187        match err {
188            PaymentsAuthError::MissingScopes { operation, required, missing } => {
189                assert_eq!(operation, PaymentOperation::CompleteCheckout);
190                assert_eq!(required, vec![PAYMENT_CHECKOUT_COMPLETE_SCOPE.to_string()]);
191                assert_eq!(missing, vec![PAYMENT_CHECKOUT_COMPLETE_SCOPE.to_string()]);
192            }
193            other => panic!("unexpected auth error: {other}"),
194        }
195    }
196}