matrix_sdk/authentication/oauth/qrcode/
messages.rs1use matrix_sdk_base::crypto::types::SecretsBundle;
16use matrix_sdk_common::deserialized_responses::PrivOwnedStr;
17use oauth2::{
18 EndUserVerificationUrl, StandardDeviceAuthorizationResponse, VerificationUriComplete,
19};
20use ruma::serde::StringEnum;
21use serde::{Deserialize, Serialize};
22use url::Url;
23use vodozemac::Curve25519PublicKey;
24
25#[cfg(doc)]
26use super::QRCodeLoginError::SecureChannel;
27
28#[derive(Debug, Serialize, Deserialize)]
31#[serde(tag = "type")]
32pub enum QrAuthMessage {
33 #[serde(rename = "m.login.protocols")]
36 LoginProtocols {
37 protocols: Vec<LoginProtocolType>,
39 homeserver: Url,
41 },
42
43 #[serde(rename = "m.login.protocol")]
46 LoginProtocol {
47 device_authorization_grant: AuthorizationGrant,
51 protocol: LoginProtocolType,
53 device_id: String,
55 },
56
57 #[serde(rename = "m.login.protocol_accepted")]
60 LoginProtocolAccepted,
61
62 #[serde(rename = "m.login.success")]
65 LoginSuccess,
66
67 #[serde(rename = "m.login.declined")]
71 LoginDeclined,
72
73 #[serde(rename = "m.login.failure")]
76 LoginFailure {
77 reason: LoginFailureReason,
79 homeserver: Option<Url>,
81 },
82
83 #[serde(rename = "m.login.secrets")]
88 LoginSecrets(SecretsBundle),
89}
90
91impl QrAuthMessage {
92 pub fn authorization_grant_login_protocol(
95 device_authorization_grant: AuthorizationGrant,
96 device_id: Curve25519PublicKey,
97 ) -> QrAuthMessage {
98 QrAuthMessage::LoginProtocol {
99 device_id: device_id.to_base64(),
100 device_authorization_grant,
101 protocol: LoginProtocolType::DeviceAuthorizationGrant,
102 }
103 }
104}
105
106impl From<&StandardDeviceAuthorizationResponse> for AuthorizationGrant {
107 fn from(value: &StandardDeviceAuthorizationResponse) -> Self {
108 Self {
109 verification_uri: value.verification_uri().clone(),
110 verification_uri_complete: value.verification_uri_complete().cloned(),
111 }
112 }
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct AuthorizationGrant {
118 pub verification_uri: EndUserVerificationUrl,
120
121 pub verification_uri_complete: Option<VerificationUriComplete>,
126}
127
128#[derive(Clone, StringEnum)]
130#[ruma_enum(rename_all = "snake_case")]
131pub enum LoginFailureReason {
132 AuthorizationExpired,
134 DeviceAlreadyExists,
137 DeviceNotFound,
140 UnexpectedMessageReceived,
143 UnsupportedProtocol,
146 UserCancelled,
149 #[doc(hidden)]
150 _Custom(PrivOwnedStr),
151}
152
153#[derive(Clone, StringEnum)]
155#[ruma_enum(rename_all = "snake_case")]
156pub enum LoginProtocolType {
157 DeviceAuthorizationGrant,
159 #[doc(hidden)]
160 _Custom(PrivOwnedStr),
161}
162
163#[cfg(test)]
164mod test {
165 use assert_matches2::assert_let;
166 use matrix_sdk_base::crypto::types::BackupSecrets;
167 use serde_json::json;
168 use similar_asserts::assert_eq;
169
170 use super::*;
171
172 #[test]
173 fn test_protocols_serialization() {
174 let json = json!({
175 "type": "m.login.protocols",
176 "protocols": ["device_authorization_grant"],
177 "homeserver": "https://matrix-client.matrix.org/"
178
179 });
180
181 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
182 assert_let!(QrAuthMessage::LoginProtocols { protocols, .. } = &message);
183 assert!(protocols.contains(&LoginProtocolType::DeviceAuthorizationGrant));
184
185 let serialized = serde_json::to_value(&message).unwrap();
186 assert_eq!(json, serialized);
187 }
188
189 #[test]
190 fn test_protocol_serialization() {
191 let json = json!({
192 "type": "m.login.protocol",
193 "protocol": "device_authorization_grant",
194 "device_authorization_grant": {
195 "verification_uri_complete": "https://id.matrix.org/device/abcde",
196 "verification_uri": "https://id.matrix.org/device/abcde?code=ABCDE"
197 },
198 "device_id": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4"
199 });
200
201 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
202 assert_let!(QrAuthMessage::LoginProtocol { protocol, device_id, .. } = &message);
203 assert_eq!(protocol, &LoginProtocolType::DeviceAuthorizationGrant);
204 assert_eq!(device_id, "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4");
205 let serialized = serde_json::to_value(&message).unwrap();
206 assert_eq!(json, serialized);
207 }
208
209 #[test]
210 fn test_protocol_accepted_serialization() {
211 let json = json!({
212 "type": "m.login.protocol_accepted",
213 });
214
215 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
216 assert_let!(QrAuthMessage::LoginProtocolAccepted = &message);
217 let serialized = serde_json::to_value(&message).unwrap();
218 assert_eq!(json, serialized);
219 }
220
221 #[test]
222 fn test_login_success() {
223 let json = json!({
224 "type": "m.login.success",
225 });
226
227 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
228 assert_let!(QrAuthMessage::LoginSuccess = &message);
229 let serialized = serde_json::to_value(&message).unwrap();
230 assert_eq!(json, serialized);
231 }
232
233 #[test]
234 fn test_login_declined() {
235 let json = json!({
236 "type": "m.login.declined",
237 });
238
239 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
240 assert_let!(QrAuthMessage::LoginDeclined = &message);
241 let serialized = serde_json::to_value(&message).unwrap();
242 assert_eq!(json, serialized);
243 }
244
245 #[test]
246 fn test_login_failure() {
247 let json = json!({
248 "type": "m.login.failure",
249 "reason": "unsupported_protocol",
250 "homeserver": "https://matrix-client.matrix.org/"
251 });
252
253 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
254 assert_let!(QrAuthMessage::LoginFailure { reason, .. } = &message);
255 assert_eq!(reason, &LoginFailureReason::UnsupportedProtocol);
256 let serialized = serde_json::to_value(&message).unwrap();
257 assert_eq!(json, serialized);
258 }
259
260 #[test]
261 fn test_login_secrets() {
262 let json = json!({
263 "type": "m.login.secrets",
264 "cross_signing": {
265 "master_key": "rTtSv67XGS6k/rg6/yTG/m573cyFTPFRqluFhQY+hSw",
266 "self_signing_key": "4jbPt7jh5D2iyM4U+3IDa+WthgJB87IQN1ATdkau+xk",
267 "user_signing_key": "YkFKtkjcsTxF6UAzIIG/l6Nog/G2RigCRfWj3cjNWeM",
268 },
269 "backup": {
270 "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
271 "backup_version": "2",
272 "key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
273 },
274 });
275
276 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
277 assert_let!(
278 QrAuthMessage::LoginSecrets(SecretsBundle { cross_signing, backup }) = &message
279 );
280 assert_eq!(cross_signing.master_key, "rTtSv67XGS6k/rg6/yTG/m573cyFTPFRqluFhQY+hSw");
281 assert_eq!(cross_signing.self_signing_key, "4jbPt7jh5D2iyM4U+3IDa+WthgJB87IQN1ATdkau+xk");
282 assert_eq!(cross_signing.user_signing_key, "YkFKtkjcsTxF6UAzIIG/l6Nog/G2RigCRfWj3cjNWeM");
283
284 assert_let!(Some(BackupSecrets::MegolmBackupV1Curve25519AesSha2(backup)) = backup);
285 assert_eq!(backup.backup_version, "2");
286 assert_eq!(&backup.key.to_base64(), "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
287
288 let serialized = serde_json::to_value(&message).unwrap();
289 assert_eq!(json, serialized);
290 }
291}