matrix_sdk/authentication/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, Deserializer, Serialize, Serializer};
22use url::Url;
23use vodozemac::Curve25519PublicKey;
24
25#[cfg(doc)]
26use crate::authentication::qrcode::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 #[serde(
54 deserialize_with = "deserialize_curve_key",
55 serialize_with = "serialize_curve_key"
56 )]
57 device_id: Curve25519PublicKey,
59 },
60
61 #[serde(rename = "m.login.protocol_accepted")]
64 LoginProtocolAccepted,
65
66 #[serde(rename = "m.login.success")]
69 LoginSuccess,
70
71 #[serde(rename = "m.login.declined")]
75 LoginDeclined,
76
77 #[serde(rename = "m.login.failure")]
80 LoginFailure {
81 reason: LoginFailureReason,
83 homeserver: Option<Url>,
85 },
86
87 #[serde(rename = "m.login.secrets")]
92 LoginSecrets(SecretsBundle),
93}
94
95impl QrAuthMessage {
96 pub fn authorization_grant_login_protocol(
99 device_authorization_grant: AuthorizationGrant,
100 device_id: Curve25519PublicKey,
101 ) -> QrAuthMessage {
102 QrAuthMessage::LoginProtocol {
103 device_id,
104 device_authorization_grant,
105 protocol: LoginProtocolType::DeviceAuthorizationGrant,
106 }
107 }
108}
109
110impl From<&StandardDeviceAuthorizationResponse> for AuthorizationGrant {
111 fn from(value: &StandardDeviceAuthorizationResponse) -> Self {
112 Self {
113 verification_uri: value.verification_uri().clone(),
114 verification_uri_complete: value.verification_uri_complete().cloned(),
115 }
116 }
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct AuthorizationGrant {
122 pub verification_uri: EndUserVerificationUrl,
124
125 pub verification_uri_complete: Option<VerificationUriComplete>,
130}
131
132#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
134#[ruma_enum(rename_all = "snake_case")]
135pub enum LoginFailureReason {
136 AuthorizationExpired,
138 DeviceAlreadyExists,
141 DeviceNotFound,
144 UnexpectedMessageReceived,
147 UnsupportedProtocol,
150 UserCancelled,
153 #[doc(hidden)]
154 _Custom(PrivOwnedStr),
155}
156
157#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
159#[ruma_enum(rename_all = "snake_case")]
160pub enum LoginProtocolType {
161 DeviceAuthorizationGrant,
163 #[doc(hidden)]
164 _Custom(PrivOwnedStr),
165}
166
167pub(crate) fn deserialize_curve_key<'de, D>(de: D) -> Result<Curve25519PublicKey, D::Error>
172where
173 D: Deserializer<'de>,
174{
175 let key: String = Deserialize::deserialize(de)?;
176
177 Curve25519PublicKey::from_base64(&key).map_err(serde::de::Error::custom)
178}
179
180pub(crate) fn serialize_curve_key<S>(key: &Curve25519PublicKey, s: S) -> Result<S::Ok, S::Error>
181where
182 S: Serializer,
183{
184 s.serialize_str(&key.to_base64())
185}
186
187#[cfg(test)]
188mod test {
189 use assert_matches2::assert_let;
190 use matrix_sdk_base::crypto::types::BackupSecrets;
191 use serde_json::json;
192 use similar_asserts::assert_eq;
193
194 use super::*;
195
196 #[test]
197 fn test_protocols_serialization() {
198 let json = json!({
199 "type": "m.login.protocols",
200 "protocols": ["device_authorization_grant"],
201 "homeserver": "https://matrix-client.matrix.org/"
202
203 });
204
205 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
206 assert_let!(QrAuthMessage::LoginProtocols { protocols, .. } = &message);
207 assert!(protocols.contains(&LoginProtocolType::DeviceAuthorizationGrant));
208
209 let serialized = serde_json::to_value(&message).unwrap();
210 assert_eq!(json, serialized);
211 }
212
213 #[test]
214 fn test_protocol_serialization() {
215 let json = json!({
216 "type": "m.login.protocol",
217 "protocol": "device_authorization_grant",
218 "device_authorization_grant": {
219 "verification_uri_complete": "https://id.matrix.org/device/abcde",
220 "verification_uri": "https://id.matrix.org/device/abcde?code=ABCDE"
221 },
222 "device_id": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4"
223 });
224 let curve_key =
225 Curve25519PublicKey::from_base64("wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4")
226 .unwrap();
227
228 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
229 assert_let!(QrAuthMessage::LoginProtocol { protocol, device_id, .. } = &message);
230 assert_eq!(protocol, &LoginProtocolType::DeviceAuthorizationGrant);
231 assert_eq!(device_id, &curve_key);
232 let serialized = serde_json::to_value(&message).unwrap();
233 assert_eq!(json, serialized);
234 }
235
236 #[test]
237 fn test_protocol_accepted_serialization() {
238 let json = json!({
239 "type": "m.login.protocol_accepted",
240 });
241
242 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
243 assert_let!(QrAuthMessage::LoginProtocolAccepted = &message);
244 let serialized = serde_json::to_value(&message).unwrap();
245 assert_eq!(json, serialized);
246 }
247
248 #[test]
249 fn test_login_success() {
250 let json = json!({
251 "type": "m.login.success",
252 });
253
254 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
255 assert_let!(QrAuthMessage::LoginSuccess = &message);
256 let serialized = serde_json::to_value(&message).unwrap();
257 assert_eq!(json, serialized);
258 }
259
260 #[test]
261 fn test_login_declined() {
262 let json = json!({
263 "type": "m.login.declined",
264 });
265
266 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
267 assert_let!(QrAuthMessage::LoginDeclined = &message);
268 let serialized = serde_json::to_value(&message).unwrap();
269 assert_eq!(json, serialized);
270 }
271
272 #[test]
273 fn test_login_failure() {
274 let json = json!({
275 "type": "m.login.failure",
276 "reason": "unsupported_protocol",
277 "homeserver": "https://matrix-client.matrix.org/"
278 });
279
280 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
281 assert_let!(QrAuthMessage::LoginFailure { reason, .. } = &message);
282 assert_eq!(reason, &LoginFailureReason::UnsupportedProtocol);
283 let serialized = serde_json::to_value(&message).unwrap();
284 assert_eq!(json, serialized);
285 }
286
287 #[test]
288 fn test_login_secrets() {
289 let json = json!({
290 "type": "m.login.secrets",
291 "cross_signing": {
292 "master_key": "rTtSv67XGS6k/rg6/yTG/m573cyFTPFRqluFhQY+hSw",
293 "self_signing_key": "4jbPt7jh5D2iyM4U+3IDa+WthgJB87IQN1ATdkau+xk",
294 "user_signing_key": "YkFKtkjcsTxF6UAzIIG/l6Nog/G2RigCRfWj3cjNWeM",
295 },
296 "backup": {
297 "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
298 "backup_version": "2",
299 "key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
300 },
301 });
302
303 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
304 assert_let!(
305 QrAuthMessage::LoginSecrets(SecretsBundle { cross_signing, backup }) = &message
306 );
307 assert_eq!(cross_signing.master_key, "rTtSv67XGS6k/rg6/yTG/m573cyFTPFRqluFhQY+hSw");
308 assert_eq!(cross_signing.self_signing_key, "4jbPt7jh5D2iyM4U+3IDa+WthgJB87IQN1ATdkau+xk");
309 assert_eq!(cross_signing.user_signing_key, "YkFKtkjcsTxF6UAzIIG/l6Nog/G2RigCRfWj3cjNWeM");
310
311 assert_let!(Some(BackupSecrets::MegolmBackupV1Curve25519AesSha2(backup)) = backup);
312 assert_eq!(backup.backup_version, "2");
313 assert_eq!(&backup.key.to_base64(), "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
314
315 let serialized = serde_json::to_value(&message).unwrap();
316 assert_eq!(json, serialized);
317 }
318}