matrix_sdk/authentication/oauth/qrcode/
mod.rs1use std::sync::Arc;
25
26use as_variant::as_variant;
27pub use matrix_sdk_base::crypto::types::qr_login::{
28 LoginQrCodeDecodeError, Msc4108IntentData, QrCodeData, QrCodeIntent, QrCodeIntentData,
29};
30use matrix_sdk_base::crypto::{SecretImportError, store::SecretsBundleExportError};
31pub use oauth2::{
32 ConfigurationError, DeviceCodeErrorResponse, DeviceCodeErrorResponseType, HttpClientError,
33 RequestTokenError, StandardErrorResponse,
34 basic::{BasicErrorResponse, BasicRequestTokenError},
35};
36use ruma::api::error::ErrorKind;
37use thiserror::Error;
38use tokio::sync::Mutex;
39use url::Url;
40pub use vodozemac::ecies::{Error as EciesError, MessageDecodeError as EciesMessageDecodeError};
41
42mod grant;
43mod login;
44mod messages;
45mod rendezvous_channel;
46mod secure_channel;
47
48pub use self::{
49 grant::{GrantLoginProgress, GrantLoginWithGeneratedQrCode, GrantLoginWithScannedQrCode},
50 login::{LoginProgress, LoginWithGeneratedQrCode, LoginWithQrCode},
51 messages::{LoginFailureReason, LoginProtocolType, QrAuthMessage},
52};
53use super::CrossProcessRefreshLockError;
54#[cfg(doc)]
55use super::OAuth;
56use crate::HttpError;
57
58#[derive(Debug, Error)]
61#[cfg_attr(feature = "uniffi", derive(uniffi::Error), uniffi(flat_error))]
62pub enum QRCodeLoginError {
63 #[error(transparent)]
66 OAuth(#[from] DeviceAuthorizationOAuthError),
67
68 #[error("The login failed, reason: {reason}")]
70 LoginFailure {
71 reason: LoginFailureReason,
73 homeserver: Option<Url>,
75 },
76
77 #[error("We have received an unexpected message, expected: {expected}, got {received:?}")]
79 UnexpectedMessage {
80 expected: &'static str,
82 received: Box<QrAuthMessage>,
84 },
85
86 #[error(transparent)]
88 SecureChannel(SecureChannelError),
89
90 #[error("The rendezvous session was not found and might have expired")]
92 NotFound,
93
94 #[error(transparent)]
96 CrossProcessRefreshLock(#[from] CrossProcessRefreshLockError),
97
98 #[error(transparent)]
102 UserIdDiscovery(HttpError),
103
104 #[error(transparent)]
107 SessionTokens(crate::Error),
108
109 #[error(transparent)]
111 DeviceKeyUpload(crate::Error),
112
113 #[error(transparent)]
116 SecretImport(#[from] SecretImportError),
117
118 #[error(transparent)]
121 ServerReset(crate::Error),
122}
123
124impl From<SecureChannelError> for QRCodeLoginError {
125 fn from(e: SecureChannelError) -> Self {
126 match e {
127 SecureChannelError::RendezvousChannel(ref http_error) => {
128 if let Some(ErrorKind::NotFound) = http_error.client_api_error_kind() {
129 return Self::NotFound;
130 }
131 Self::SecureChannel(e)
132 }
133 e => Self::SecureChannel(e),
134 }
135 }
136}
137
138#[derive(Debug, Error)]
141pub enum QRCodeGrantLoginError {
142 #[error("Secrets backup not set up")]
144 MissingSecretsBackup(Option<SecretsBundleExportError>),
145
146 #[error("The check code was incorrect")]
148 InvalidCheckCode,
149
150 #[error("The rendezvous session was not found and might have expired")]
152 NotFound,
153
154 #[error("Auth handshake error: {0}")]
156 Unknown(String),
157
158 #[error("Unsupported protocol: {0}")]
160 UnsupportedProtocol(LoginProtocolType),
161
162 #[error("The requested device ID is already in use")]
164 DeviceIDAlreadyInUse,
165
166 #[error("The requested device was not returned by the homeserver")]
168 DeviceNotFound,
169
170 #[error(transparent)]
172 SecureChannel(SecureChannelError),
173
174 #[error("We have received an unexpected message, expected: {expected}, got {received:?}")]
176 UnexpectedMessage {
177 expected: &'static str,
179 received: Box<QrAuthMessage>,
181 },
182
183 #[error("The login failed, reason: {reason}")]
185 LoginFailure {
186 reason: LoginFailureReason,
188 },
189}
190
191impl From<SecureChannelError> for QRCodeGrantLoginError {
192 fn from(e: SecureChannelError) -> Self {
193 match e {
194 SecureChannelError::RendezvousChannel(ref http_error) => {
195 if let Some(ErrorKind::NotFound) = http_error.client_api_error_kind() {
196 return Self::NotFound;
197 }
198 Self::SecureChannel(e)
199 }
200 SecureChannelError::InvalidCheckCode => Self::InvalidCheckCode,
201 e => Self::SecureChannel(e),
202 }
203 }
204}
205
206impl From<SecretsBundleExportError> for QRCodeGrantLoginError {
207 fn from(e: SecretsBundleExportError) -> Self {
208 Self::MissingSecretsBackup(Some(e))
209 }
210}
211
212#[derive(Debug, Error)]
215pub enum DeviceAuthorizationOAuthError {
216 #[error(transparent)]
219 OAuth(#[from] crate::authentication::oauth::OAuthError),
220
221 #[error("OAuth 2.0 server doesn't support the device authorization grant")]
223 NoDeviceAuthorizationEndpoint,
224
225 #[error(transparent)]
228 DeviceAuthorization(#[from] BasicRequestTokenError<HttpClientError<reqwest::Error>>),
229
230 #[error(transparent)]
233 RequestToken(
234 #[from] RequestTokenError<HttpClientError<reqwest::Error>, DeviceCodeErrorResponse>,
235 ),
236}
237
238impl DeviceAuthorizationOAuthError {
239 pub fn as_request_token_error(&self) -> Option<&DeviceCodeErrorResponseType> {
242 let error = as_variant!(self, DeviceAuthorizationOAuthError::RequestToken)?;
243 let request_token_error = as_variant!(error, RequestTokenError::ServerResponse)?;
244
245 Some(request_token_error.error())
246 }
247}
248
249#[derive(Debug, Error)]
252pub enum MessageDecodeError {
253 #[error(transparent)]
255 Ecies(#[from] EciesMessageDecodeError),
256 #[error(transparent)]
259 Utf8(#[from] std::str::Utf8Error),
260 #[error(transparent)]
262 Json(#[from] serde_json::Error),
263}
264
265#[derive(Debug, Error)]
267pub enum DecryptionError {
268 #[error(transparent)]
270 Ecies(#[from] EciesError),
271}
272
273#[derive(Debug, Error)]
276pub enum SecureChannelError {
277 #[error(transparent)]
279 Decryption(#[from] DecryptionError),
280
281 #[error(transparent)]
283 MessageDecode(#[from] MessageDecodeError),
284
285 #[error(
288 "The secure channel setup has received an unexpected message, expected: {expected}, got {received}"
289 )]
290 SecureChannelMessage {
291 expected: &'static str,
293 received: String,
295 },
296
297 #[error("The secure channel could not have been established, the check code was invalid")]
300 InvalidCheckCode,
301
302 #[error("Error in the rendezvous channel: {0:?}")]
304 RendezvousChannel(#[from] HttpError),
305
306 #[error(
309 "The secure channel could not have been established, \
310 the two devices have the same login intent"
311 )]
312 InvalidIntent,
313
314 #[error(
317 "The secure channel could not have been established, \
318 the check code cannot be received"
319 )]
320 CannotReceiveCheckCode,
321
322 #[error("The QR code specifies an unsupported protocol version")]
323 UnsupportedQrCodeType,
325}
326
327#[derive(Clone, Debug)]
335pub struct QrProgress {
336 pub check_code: u8,
338}
339
340#[derive(Clone, Debug)]
349pub enum GeneratedQrProgress {
350 QrReady(QrCodeData),
353 QrScanned(CheckCodeSender),
357}
358
359pub type CheckCodeSender = CloneableSender<u8>;
362
363impl CheckCodeSender {
364 pub async fn send(&self, check_code: u8) -> Result<(), SenderError> {
372 self.send_impl(check_code).await
373 }
374}
375
376#[derive(Clone, Copy, Debug)]
379pub(crate) enum ContinuationMessage {
380 Confirm,
381 Cancel,
382}
383
384#[derive(Clone, Debug)]
388pub struct ContinuationMessageSender(CloneableSender<ContinuationMessage>);
389
390impl ContinuationMessageSender {
391 pub async fn confirm(&self) -> Result<(), SenderError> {
393 self.0.send_impl(ContinuationMessage::Confirm).await
394 }
395
396 pub async fn cancel(&self) -> Result<(), SenderError> {
398 self.0.send_impl(ContinuationMessage::Cancel).await
399 }
400}
401
402#[derive(Clone, Debug)]
405pub struct CloneableSender<T> {
406 inner: Arc<Mutex<Option<tokio::sync::oneshot::Sender<T>>>>,
407}
408
409impl<T> CloneableSender<T> {
410 pub(crate) fn new(tx: tokio::sync::oneshot::Sender<T>) -> Self {
411 Self { inner: Arc::new(Mutex::new(Some(tx))) }
412 }
413
414 async fn send_impl(&self, message: T) -> Result<(), SenderError> {
415 match self.inner.lock().await.take() {
416 Some(tx) => tx.send(message).map_err(|_| SenderError::CannotSend),
417 None => Err(SenderError::AlreadySent),
418 }
419 }
420}
421
422#[derive(Debug, thiserror::Error)]
424pub enum SenderError {
425 #[error("message already sent.")]
427 AlreadySent,
428 #[error("message cannot be sent.")]
430 CannotSend,
431}
432
433#[cfg(all(test, not(target_family = "wasm")))]
434mod tests {
435 use matrix_sdk_test::async_test;
436 use serde_json::json;
437 use wiremock::{
438 Mock, ResponseTemplate,
439 matchers::{method, path},
440 };
441
442 use crate::test_utils::mocks::MatrixMockServer;
443
444 #[async_test]
445 async fn test_msc_4388_rendezvous_server_supported() {
446 const URL: &str = "/_matrix/client/unstable/io.element.msc4388/rendezvous";
447
448 let server = MatrixMockServer::new().await;
449 let client = server.client_builder().logged_in_with_oauth().build().await;
450
451 {
452 let _discover_guard = server
453 .server()
454 .register_as_scoped(
455 Mock::given(method("GET"))
456 .and(path(URL))
457 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
458 "create_available": true,
459 })))
460 .expect(1),
461 )
462 .await;
463
464 let supported = client
465 .oauth()
466 .msc_4388_rendezvous_server_supported()
467 .await
468 .expect("We should be able to check if the rendezvous server is supported");
469
470 assert!(supported, "The rendezvous server should be supported");
471 }
472
473 {
474 let _discover_guard = server
475 .server()
476 .register_as_scoped(
477 Mock::given(method("GET"))
478 .and(path(URL))
479 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
480 "create_available": false,
481 })))
482 .expect(1),
483 )
484 .await;
485
486 let supported = client
487 .oauth()
488 .msc_4388_rendezvous_server_supported()
489 .await
490 .expect("We should be able to check if the rendezvous server is supported");
491
492 assert!(
493 !supported,
494 "The rendezvous server should not be supported, because create_available is false"
495 );
496 }
497
498 {
499 let _discover_guard = server
500 .server()
501 .register_as_scoped(
502 Mock::given(method("GET"))
503 .and(path(URL))
504 .respond_with(ResponseTemplate::new(404))
505 .expect(1),
506 )
507 .await;
508
509 let supported = client
510 .oauth()
511 .msc_4388_rendezvous_server_supported()
512 .await
513 .expect("We should be able to check if the rendezvous server is supported");
514
515 assert!(
516 !supported,
517 "The rendezvous server should not be supported if we receive a 404 response"
518 );
519 }
520
521 {
522 let _discover_guard = server
523 .server()
524 .register_as_scoped(
525 Mock::given(method("GET"))
526 .and(path(URL))
527 .respond_with(ResponseTemplate::new(403))
528 .expect(1),
529 )
530 .await;
531
532 let supported = client
533 .oauth()
534 .msc_4388_rendezvous_server_supported()
535 .await
536 .expect("We should be able to check if the rendezvous server is supported");
537
538 assert!(
539 !supported,
540 "The rendezvous server should not be supported if we receive a 403 response"
541 );
542 }
543
544 {
545 let _discover_guard = server
546 .server()
547 .register_as_scoped(
548 Mock::given(method("GET"))
549 .and(path(URL))
550 .respond_with(ResponseTemplate::new(500))
551 .expect(1),
552 )
553 .await;
554
555 client
556 .oauth()
557 .msc_4388_rendezvous_server_supported()
558 .await
559 .expect_err("We should return an error if the homeserver can't tell us if the endpoint is supported or not");
560 }
561 }
562}