cratestack_client_rust/step_up/
mod.rs1use serde_json::Value as Json;
14
15use crate::error::ClientError;
16use crate::rpc::error::{RpcClientError, RpcRemoteError};
17
18mod retry;
19
20pub use retry::retry_after_step_up;
21
22pub const INSUFFICIENT_USER_AUTHENTICATION: &str = "insufficient_user_authentication";
24
25#[derive(Debug, Clone, PartialEq, Eq, Default)]
28pub struct StepUpRequired {
29 pub max_age: Option<u64>,
31 pub acr_values: Vec<String>,
33 pub amr: Vec<String>,
35 pub unmet: Vec<String>,
38}
39
40impl StepUpRequired {
41 fn from_refusal(details: Option<Json>, error_member: Option<&Json>) -> Option<Self> {
45 if error_member.and_then(Json::as_str) != Some(INSUFFICIENT_USER_AUTHENTICATION) {
46 return None;
47 }
48 let details = match details {
49 Some(Json::Object(details)) => details,
50 _ => Default::default(),
51 };
52 let strings = |name: &str| -> Vec<String> {
53 match details.get(name) {
54 Some(Json::Array(items)) => items
55 .iter()
56 .filter_map(|item| item.as_str().map(str::to_owned))
57 .collect(),
58 _ => Vec::new(),
59 }
60 };
61 Some(Self {
62 max_age: details.get("max_age").and_then(Json::as_u64),
63 acr_values: strings("acr_values"),
64 amr: strings("amr"),
65 unmet: strings("unmet"),
66 })
67 }
68}
69
70pub trait StepUpError {
74 fn step_up(&self) -> Option<StepUpRequired>;
76}
77
78impl ClientError {
79 pub fn step_up(&self) -> Option<StepUpRequired> {
83 let ClientError::Remote { status, error, .. } = self else {
84 return None;
85 };
86 if status.as_u16() != 401 {
87 return None;
88 }
89 let error = error.as_deref()?;
90 let member = error.extra.get("error").map(|value| value.to_plain_json());
91 let details = error.details.as_ref().map(|value| value.to_plain_json());
92 StepUpRequired::from_refusal(details, member.as_ref())
93 }
94}
95
96impl RpcRemoteError {
97 pub fn step_up(&self) -> Option<StepUpRequired> {
101 if self.body.code != "unauthenticated" {
102 return None;
103 }
104 StepUpRequired::from_refusal(self.body.details.clone(), self.body.extra.get("error"))
105 }
106}
107
108impl RpcClientError {
109 pub fn step_up(&self) -> Option<StepUpRequired> {
112 self.remote().and_then(RpcRemoteError::step_up)
113 }
114}
115
116impl StepUpError for ClientError {
117 fn step_up(&self) -> Option<StepUpRequired> {
118 ClientError::step_up(self)
119 }
120}
121
122impl StepUpError for RpcRemoteError {
123 fn step_up(&self) -> Option<StepUpRequired> {
124 RpcRemoteError::step_up(self)
125 }
126}
127
128impl StepUpError for RpcClientError {
129 fn step_up(&self) -> Option<StepUpRequired> {
130 RpcClientError::step_up(self)
131 }
132}
133
134#[cfg(test)]
135mod tests;