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,
44 },
45
46 #[serde(rename = "m.login.protocol")]
49 LoginProtocol {
50 device_authorization_grant: AuthorizationGrant,
54 protocol: LoginProtocolType,
56 device_id: String,
58 },
59
60 #[serde(rename = "m.login.protocol_accepted")]
63 LoginProtocolAccepted,
64
65 #[serde(rename = "m.login.success")]
68 LoginSuccess,
69
70 #[serde(rename = "m.login.declined")]
74 LoginDeclined,
75
76 #[serde(rename = "m.login.failure")]
79 LoginFailure {
80 reason: LoginFailureReason,
82 homeserver: Option<Url>,
84 },
85
86 #[serde(rename = "m.login.secrets")]
91 LoginSecrets(SecretsBundle),
92}
93
94impl QrAuthMessage {
95 pub fn authorization_grant_login_protocol(
98 device_authorization_grant: AuthorizationGrant,
99 device_id: Curve25519PublicKey,
100 ) -> QrAuthMessage {
101 QrAuthMessage::LoginProtocol {
102 device_id: device_id.to_base64(),
103 device_authorization_grant,
104 protocol: LoginProtocolType::DeviceAuthorizationGrant,
105 }
106 }
107}
108
109impl From<&StandardDeviceAuthorizationResponse> for AuthorizationGrant {
110 fn from(value: &StandardDeviceAuthorizationResponse) -> Self {
111 Self {
112 verification_uri: value.verification_uri().clone(),
113 verification_uri_complete: value.verification_uri_complete().cloned(),
114 }
115 }
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct AuthorizationGrant {
121 pub verification_uri: EndUserVerificationUrl,
123
124 pub verification_uri_complete: Option<VerificationUriComplete>,
129}
130
131#[derive(Clone, StringEnum)]
133#[ruma_enum(rename_all = "snake_case")]
134pub enum LoginFailureReason {
135 AuthorizationExpired,
137 DeviceAlreadyExists,
140 DeviceNotFound,
143 UnexpectedMessageReceived,
146 UnsupportedProtocol,
149 UserCancelled,
152 #[doc(hidden)]
153 _Custom(PrivOwnedStr),
154}
155
156#[derive(Clone, StringEnum)]
158#[ruma_enum(rename_all = "snake_case")]
159pub enum LoginProtocolType {
160 DeviceAuthorizationGrant,
162 #[doc(hidden)]
163 _Custom(PrivOwnedStr),
164}
165
166#[cfg(test)]
167mod test {
168 use matrix_sdk_base::crypto::types::BackupSecrets;
169 use serde_json::json;
170 use similar_asserts::assert_eq;
171 use strass::assert_let;
172
173 use super::*;
174
175 #[test]
176 fn test_protocols_serialization() {
177 let json = json!({
178 "type": "m.login.protocols",
179 "protocols": ["device_authorization_grant"],
180 "homeserver": "https://matrix-client.matrix.org/"
181
182 });
183
184 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
185 assert_let!(QrAuthMessage::LoginProtocols { protocols, .. } = &message);
186 assert!(protocols.contains(&LoginProtocolType::DeviceAuthorizationGrant));
187
188 let serialized = serde_json::to_value(&message).unwrap();
189 assert_eq!(json, serialized);
190 }
191
192 #[test]
193 fn test_protocol_serialization() {
194 let json = json!({
195 "type": "m.login.protocol",
196 "protocol": "device_authorization_grant",
197 "device_authorization_grant": {
198 "verification_uri_complete": "https://id.matrix.org/device/abcde",
199 "verification_uri": "https://id.matrix.org/device/abcde?code=ABCDE"
200 },
201 "device_id": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4"
202 });
203
204 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
205 assert_let!(QrAuthMessage::LoginProtocol { protocol, device_id, .. } = &message);
206 assert_eq!(protocol, &LoginProtocolType::DeviceAuthorizationGrant);
207 assert_eq!(device_id, "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4");
208 let serialized = serde_json::to_value(&message).unwrap();
209 assert_eq!(json, serialized);
210 }
211
212 #[test]
213 fn test_protocol_accepted_serialization() {
214 let json = json!({
215 "type": "m.login.protocol_accepted",
216 });
217
218 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
219 assert_let!(QrAuthMessage::LoginProtocolAccepted = &message);
220 let serialized = serde_json::to_value(&message).unwrap();
221 assert_eq!(json, serialized);
222 }
223
224 #[test]
225 fn test_login_success() {
226 let json = json!({
227 "type": "m.login.success",
228 });
229
230 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
231 assert_let!(QrAuthMessage::LoginSuccess = &message);
232 let serialized = serde_json::to_value(&message).unwrap();
233 assert_eq!(json, serialized);
234 }
235
236 #[test]
237 fn test_login_declined() {
238 let json = json!({
239 "type": "m.login.declined",
240 });
241
242 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
243 assert_let!(QrAuthMessage::LoginDeclined = &message);
244 let serialized = serde_json::to_value(&message).unwrap();
245 assert_eq!(json, serialized);
246 }
247
248 #[test]
249 fn test_login_failure() {
250 let json = json!({
251 "type": "m.login.failure",
252 "reason": "unsupported_protocol",
253 "homeserver": "https://matrix-client.matrix.org/"
254 });
255
256 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
257 assert_let!(QrAuthMessage::LoginFailure { reason, .. } = &message);
258 assert_eq!(reason, &LoginFailureReason::UnsupportedProtocol);
259 let serialized = serde_json::to_value(&message).unwrap();
260 assert_eq!(json, serialized);
261 }
262
263 #[test]
264 fn test_login_secrets() {
265 let json = json!({
266 "type": "m.login.secrets",
267 "cross_signing": {
268 "master_key": "rTtSv67XGS6k/rg6/yTG/m573cyFTPFRqluFhQY+hSw",
269 "self_signing_key": "4jbPt7jh5D2iyM4U+3IDa+WthgJB87IQN1ATdkau+xk",
270 "user_signing_key": "YkFKtkjcsTxF6UAzIIG/l6Nog/G2RigCRfWj3cjNWeM",
271 },
272 "backup": {
273 "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
274 "backup_version": "2",
275 "key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
276 },
277 });
278
279 let message: QrAuthMessage = serde_json::from_value(json.clone()).unwrap();
280 assert_let!(
281 QrAuthMessage::LoginSecrets(SecretsBundle { cross_signing, backup }) = &message
282 );
283 assert_eq!(cross_signing.master_key, "rTtSv67XGS6k/rg6/yTG/m573cyFTPFRqluFhQY+hSw");
284 assert_eq!(cross_signing.self_signing_key, "4jbPt7jh5D2iyM4U+3IDa+WthgJB87IQN1ATdkau+xk");
285 assert_eq!(cross_signing.user_signing_key, "YkFKtkjcsTxF6UAzIIG/l6Nog/G2RigCRfWj3cjNWeM");
286
287 assert_let!(Some(BackupSecrets::MegolmBackupV1Curve25519AesSha2(backup)) = backup);
288 assert_eq!(backup.backup_version, "2");
289 assert_eq!(&backup.key.to_base64(), "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
290
291 let serialized = serde_json::to_value(&message).unwrap();
292 assert_eq!(json, serialized);
293 }
294}