graph_oauth/identity/device_authorization_response.rs
1use std::collections::{BTreeSet, HashMap};
2use std::fmt::{Display, Formatter};
3use std::str::FromStr;
4
5use serde_json::Value;
6
7#[cfg(feature = "interactive-auth")]
8use graph_core::http::JsonHttpResponse;
9
10#[cfg(feature = "interactive-auth")]
11use crate::interactive::WindowCloseReason;
12
13#[cfg(feature = "interactive-auth")]
14use crate::identity::{DeviceCodeCredential, PublicClientApplication};
15
16/// The Device Authorization Response: the authorization server generates a unique device
17/// verification code and an end-user code that are valid for a limited time and includes
18/// them in the HTTP response body using the "application/json" format [RFC8259] with a
19/// 200 (OK) status code
20///
21/// The actual [device code response](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code#device-authorization-response)
22/// that is received from Microsoft Graph does not include the verification_uri_complete field
23/// even though it's in the [specification](https://datatracker.ietf.org/doc/html/rfc8628#section-3.2).
24/// The device code response from Microsoft Graph looks like similar to the following:
25///
26/// ```json
27/// {
28/// "device_code": String("FABABAAEAAAD--DLA3VO7QrddgJg7WevrgJ7Czy_TDsDClt2ELoEC8ePWFs"),
29/// "expires_in": Number(900),
30/// "interval": Number(5),
31/// "message": String("To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code FQK5HW3UF to authenticate."),
32/// "user_code": String("FQK5HW3UF"),
33/// "verification_uri": String("https://microsoft.com/devicelogin"),
34/// }
35/// ```
36#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
37pub struct DeviceAuthorizationResponse {
38 /// A long string used to verify the session between the client and the authorization server.
39 /// The client uses this parameter to request the access token from the authorization server.
40 pub device_code: String,
41 /// The number of seconds before the device_code and user_code expire.
42 pub expires_in: u64,
43 /// OPTIONAL
44 /// The minimum amount of time in seconds that the client
45 /// SHOULD wait between polling requests to the token endpoint. If no
46 /// value is provided, clients MUST use 5 as the default.
47 #[serde(default = "default_interval")]
48 pub interval: u64,
49 /// User friendly text response that can be used for display purpose.
50 pub message: String,
51 /// A short string shown to the user that's used to identify the session on a secondary device.
52 pub user_code: String,
53 /// Verification URL where the user must navigate to authenticate using the device code
54 /// and credentials.
55 pub verification_uri: String,
56 /// The verification_uri_complete response field is not included or supported
57 /// by Microsoft at this time. It is included here because it is part of the
58 /// [standard](https://datatracker.ietf.org/doc/html/rfc8628) and in the case
59 /// that Microsoft decides to include it.
60 pub verification_uri_complete: Option<String>,
61 /// List of the scopes that would be held by token.
62 pub scopes: Option<BTreeSet<String>>,
63 #[serde(flatten)]
64 pub additional_fields: HashMap<String, Value>,
65}
66
67fn default_interval() -> u64 {
68 5
69}
70
71impl Display for DeviceAuthorizationResponse {
72 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
73 write!(
74 f,
75 "{}, {}, {}, {}, {}, {}, {:#?}, {:#?}",
76 self.device_code,
77 self.expires_in,
78 self.interval,
79 self.message,
80 self.user_code,
81 self.verification_uri,
82 self.verification_uri_complete,
83 self.scopes
84 )
85 }
86}
87
88/// Response types used when polling for a device code
89/// https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
90#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
91pub enum PollDeviceCodeEvent {
92 /// The user hasn't finished authenticating, but hasn't canceled the flow.
93 /// Repeat the request after at least interval seconds.
94 AuthorizationPending,
95 /// The end user denied the authorization request.
96 /// Stop polling and revert to an unauthenticated state.
97 AuthorizationDeclined,
98 /// The device_code sent to the /token endpoint wasn't recognized.
99 /// Verify that the client is sending the correct device_code in the request.
100 BadVerificationCode,
101 /// Value of expires_in has been exceeded and authentication is no longer possible
102 /// with device_code.
103 /// Stop polling and revert to an unauthenticated state.
104 ExpiredToken,
105
106 /// Not yet supported by Microsoft but listed in the specification.
107 ///
108 /// The authorization request was denied.
109 AccessDenied,
110
111 /// Not yet supported by Microsoft but listed in the specification.
112 ///
113 /// A variant of "authorization_pending", the authorization request is
114 /// still pending and polling should continue, but the interval MUST
115 /// be increased by 5 seconds for this and all subsequent requests.
116 SlowDown,
117}
118
119impl PollDeviceCodeEvent {
120 pub fn as_str(&self) -> &'static str {
121 match self {
122 PollDeviceCodeEvent::AuthorizationPending => "authorization_pending",
123 PollDeviceCodeEvent::AuthorizationDeclined => "authorization_declined",
124 PollDeviceCodeEvent::BadVerificationCode => "bad_verification_code",
125 PollDeviceCodeEvent::ExpiredToken => "expired_token",
126 PollDeviceCodeEvent::AccessDenied => "access_denied",
127 PollDeviceCodeEvent::SlowDown => "slow_down",
128 }
129 }
130}
131
132impl FromStr for PollDeviceCodeEvent {
133 type Err = ();
134
135 fn from_str(s: &str) -> Result<Self, Self::Err> {
136 match s {
137 "authorization_pending" => Ok(PollDeviceCodeEvent::AuthorizationPending),
138 "authorization_declined" => Ok(PollDeviceCodeEvent::AuthorizationDeclined),
139 "bad_verification_code" => Ok(PollDeviceCodeEvent::BadVerificationCode),
140 "expired_token" => Ok(PollDeviceCodeEvent::ExpiredToken),
141 "access_denied" => Ok(PollDeviceCodeEvent::AccessDenied),
142 "slow_down" => Ok(PollDeviceCodeEvent::SlowDown),
143 _ => Err(()),
144 }
145 }
146}
147
148impl AsRef<str> for PollDeviceCodeEvent {
149 fn as_ref(&self) -> &str {
150 self.as_str()
151 }
152}
153
154impl Display for PollDeviceCodeEvent {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 write!(f, "{}", self.as_str())
157 }
158}
159
160#[cfg(feature = "interactive-auth")]
161#[derive(Debug)]
162pub enum InteractiveDeviceCodeEvent {
163 DeviceAuthorizationResponse {
164 response: JsonHttpResponse,
165 device_authorization_response: Option<DeviceAuthorizationResponse>,
166 },
167 PollDeviceCode {
168 poll_device_code_event: PollDeviceCodeEvent,
169 response: JsonHttpResponse,
170 },
171 WindowClosed(WindowCloseReason),
172 SuccessfulAuthEvent {
173 response: JsonHttpResponse,
174 public_application: PublicClientApplication<DeviceCodeCredential>,
175 },
176}