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::{
31 SecretImportError,
32 store::SecretsBundleExportError,
33 types::qr_login::{InvalidLengthError, QrCodeCreationError},
34};
35pub use oauth2::{
36 ConfigurationError, DeviceCodeErrorResponse, DeviceCodeErrorResponseType, HttpClientError,
37 RequestTokenError, StandardErrorResponse,
38 basic::{BasicErrorResponse, BasicRequestTokenError},
39};
40use ruma::api::error::ErrorKind;
41use thiserror::Error;
42use tokio::sync::Mutex;
43pub use vodozemac::{
44 ecies::{Error as EciesError, MessageDecodeError as EciesMessageDecodeError},
45 hpke::{Error as HpkeError, MessageDecodeError as HpkeMessageDecodeError},
46};
47
48mod grant;
49mod login;
50mod messages;
51mod rendezvous_channel;
52mod secure_channel;
53
54pub use self::{
55 grant::{GrantLoginProgress, GrantLoginWithGeneratedQrCode, GrantLoginWithScannedQrCode},
56 login::{LoginProgress, LoginWithGeneratedQrCode, LoginWithQrCode},
57 messages::{
58 AuthorizationGrant, LoginFailureReason, LoginProtocolData, LoginProtocolType,
59 QrAuthMessage, UnknownLoginProtocol,
60 },
61};
62use super::CrossProcessRefreshLockError;
63#[cfg(doc)]
64use super::OAuth;
65use crate::HttpError;
66
67#[derive(Debug, Error)]
70#[cfg_attr(feature = "uniffi", derive(uniffi::Error), uniffi(flat_error))]
71pub enum QRCodeLoginError {
72 #[error(transparent)]
75 OAuth(#[from] DeviceAuthorizationOAuthError),
76
77 #[error("The login failed, reason: {reason}")]
79 LoginFailure {
80 reason: LoginFailureReason,
82 homeserver: Option<String>,
85 },
86
87 #[error("We have received an unexpected message, expected: {expected}, got {received:?}")]
89 UnexpectedMessage {
90 expected: &'static str,
92 received: Box<QrAuthMessage>,
94 },
95
96 #[error(transparent)]
98 SecureChannel(SecureChannelError),
99
100 #[error("The rendezvous session was not found and might have expired")]
102 NotFound,
103
104 #[error(transparent)]
106 CrossProcessRefreshLock(#[from] CrossProcessRefreshLockError),
107
108 #[error(transparent)]
112 UserIdDiscovery(HttpError),
113
114 #[error(transparent)]
117 SessionTokens(crate::Error),
118
119 #[error(transparent)]
121 DeviceKeyUpload(crate::Error),
122
123 #[error(transparent)]
126 SecretImport(#[from] SecretImportError),
127
128 #[error(transparent)]
131 ServerReset(crate::Error),
132}
133
134impl From<SecureChannelError> for QRCodeLoginError {
135 fn from(e: SecureChannelError) -> Self {
136 match e {
137 SecureChannelError::RendezvousChannel(ref http_error) => {
138 if let Some(ErrorKind::NotFound) = http_error.client_api_error_kind() {
139 return Self::NotFound;
140 }
141 Self::SecureChannel(e)
142 }
143 e => Self::SecureChannel(e),
144 }
145 }
146}
147
148#[derive(Debug, Error)]
151pub enum QRCodeGrantLoginError {
152 #[error("Secrets backup not set up")]
154 MissingSecretsBackup(Option<SecretsBundleExportError>),
155
156 #[error("The check code was incorrect")]
158 InvalidCheckCode,
159
160 #[error("The rendezvous session was not found and might have expired")]
162 NotFound,
163
164 #[error("Auth handshake error: {0}")]
166 Unknown(String),
167
168 #[error("Unsupported protocol: {0}")]
170 UnsupportedProtocol(LoginProtocolType),
171
172 #[error("The requested device ID is already in use")]
174 DeviceIDAlreadyInUse,
175
176 #[error("The requested device was not returned by the homeserver")]
178 DeviceNotFound,
179
180 #[error(transparent)]
182 SecureChannel(SecureChannelError),
183
184 #[error("We have received an unexpected message, expected: {expected}, got {received:?}")]
186 UnexpectedMessage {
187 expected: &'static str,
189 received: Box<QrAuthMessage>,
191 },
192
193 #[error("The login failed, reason: {reason}")]
195 LoginFailure {
196 reason: LoginFailureReason,
198 },
199}
200
201impl From<SecureChannelError> for QRCodeGrantLoginError {
202 fn from(e: SecureChannelError) -> Self {
203 match e {
204 SecureChannelError::RendezvousChannel(ref http_error) => {
205 if let Some(ErrorKind::NotFound) = http_error.client_api_error_kind() {
206 return Self::NotFound;
207 }
208 Self::SecureChannel(e)
209 }
210 SecureChannelError::InvalidCheckCode => Self::InvalidCheckCode,
211 e => Self::SecureChannel(e),
212 }
213 }
214}
215
216impl From<SecretsBundleExportError> for QRCodeGrantLoginError {
217 fn from(e: SecretsBundleExportError) -> Self {
218 Self::MissingSecretsBackup(Some(e))
219 }
220}
221
222#[derive(Debug, Error)]
225pub enum DeviceAuthorizationOAuthError {
226 #[error(transparent)]
229 OAuth(#[from] crate::authentication::oauth::OAuthError),
230
231 #[error("OAuth 2.0 server doesn't support the device authorization grant")]
233 NoDeviceAuthorizationEndpoint,
234
235 #[error(transparent)]
238 DeviceAuthorization(#[from] BasicRequestTokenError<HttpClientError<reqwest::Error>>),
239
240 #[error(transparent)]
243 RequestToken(
244 #[from] RequestTokenError<HttpClientError<reqwest::Error>, DeviceCodeErrorResponse>,
245 ),
246}
247
248impl DeviceAuthorizationOAuthError {
249 pub fn as_request_token_error(&self) -> Option<&DeviceCodeErrorResponseType> {
252 let error = as_variant!(self, DeviceAuthorizationOAuthError::RequestToken)?;
253 let request_token_error = as_variant!(error, RequestTokenError::ServerResponse)?;
254
255 Some(request_token_error.error())
256 }
257}
258
259#[derive(Debug, Error)]
262pub enum MessageDecodeError {
263 #[error(transparent)]
265 Ecies(#[from] EciesMessageDecodeError),
266
267 #[error(transparent)]
269 Hpke(#[from] HpkeMessageDecodeError),
270
271 #[error(transparent)]
274 Utf8(#[from] std::str::Utf8Error),
275
276 #[error(transparent)]
278 Json(#[from] serde_json::Error),
279
280 #[error("The sequence token is too long")]
284 TooLongSequenceToken(InvalidLengthError),
285
286 #[error("The base URL of the homeserver is too long")]
289 TooLongBaseUrl(InvalidLengthError),
290
291 #[error("The rendezvous ID is too long")]
294 TooLongRendezvousId(InvalidLengthError),
295}
296
297#[derive(Debug, Error)]
299pub enum DecryptionError {
300 #[error(transparent)]
302 Ecies(#[from] EciesError),
303 #[error(transparent)]
305 Hpke(#[from] HpkeError),
306}
307
308#[derive(Debug, Error)]
311pub enum SecureChannelError {
312 #[error(transparent)]
314 Decryption(#[from] DecryptionError),
315
316 #[error(transparent)]
318 MessageDecode(#[from] MessageDecodeError),
319
320 #[error(
323 "The secure channel setup has received an unexpected message, expected: {expected}, got {received}"
324 )]
325 SecureChannelMessage {
326 expected: &'static str,
328 received: String,
330 },
331
332 #[error("The secure channel could not have been established, the check code was invalid")]
335 InvalidCheckCode,
336
337 #[error("Error in the rendezvous channel: {0:?}")]
339 RendezvousChannel(#[from] HttpError),
340
341 #[error(
344 "The secure channel could not have been established, \
345 the two devices have the same login intent"
346 )]
347 InvalidIntent,
348
349 #[error(
352 "The secure channel could not have been established, \
353 the check code cannot be received"
354 )]
355 CannotReceiveCheckCode,
356
357 #[error("The QR code specifies an unsupported protocol version")]
359 UnsupportedQrCodeType,
360
361 #[error(transparent)]
363 QrCodeCreationError(#[from] QrCodeCreationError),
364}
365
366#[derive(Clone, Debug)]
374pub struct QrProgress {
375 pub check_code: u8,
377}
378
379#[derive(Clone, Debug)]
388pub enum GeneratedQrProgress {
389 QrReady(QrCodeData),
392 QrScanned(CheckCodeSender),
396}
397
398pub type CheckCodeSender = CloneableSender<u8>;
401
402impl CheckCodeSender {
403 pub async fn send(&self, check_code: u8) -> Result<(), SenderError> {
411 self.send_impl(check_code).await
412 }
413}
414
415#[derive(Clone, Copy, Debug)]
418pub(crate) enum ContinuationMessage {
419 Confirm,
420 Cancel,
421}
422
423#[derive(Clone, Debug)]
427pub struct ContinuationMessageSender(CloneableSender<ContinuationMessage>);
428
429impl ContinuationMessageSender {
430 pub async fn confirm(&self) -> Result<(), SenderError> {
432 self.0.send_impl(ContinuationMessage::Confirm).await
433 }
434
435 pub async fn cancel(&self) -> Result<(), SenderError> {
437 self.0.send_impl(ContinuationMessage::Cancel).await
438 }
439}
440
441#[derive(Clone, Debug)]
444pub struct CloneableSender<T> {
445 inner: Arc<Mutex<Option<tokio::sync::oneshot::Sender<T>>>>,
446}
447
448impl<T> CloneableSender<T> {
449 pub(crate) fn new(tx: tokio::sync::oneshot::Sender<T>) -> Self {
450 Self { inner: Arc::new(Mutex::new(Some(tx))) }
451 }
452
453 async fn send_impl(&self, message: T) -> Result<(), SenderError> {
454 match self.inner.lock().await.take() {
455 Some(tx) => tx.send(message).map_err(|_| SenderError::CannotSend),
456 None => Err(SenderError::AlreadySent),
457 }
458 }
459}
460
461#[derive(Debug, thiserror::Error)]
463pub enum SenderError {
464 #[error("message already sent.")]
466 AlreadySent,
467 #[error("message cannot be sent.")]
469 CannotSend,
470}
471
472#[cfg(all(test, not(target_family = "wasm")))]
473mod tests {
474 use matrix_sdk_test::async_test;
475 use serde_json::json;
476 use wiremock::{
477 Mock, ResponseTemplate,
478 matchers::{method, path},
479 };
480
481 use crate::test_utils::mocks::MatrixMockServer;
482
483 #[async_test]
484 async fn test_msc_4388_rendezvous_server_supported() {
485 const URL: &str = "/_matrix/client/unstable/io.element.msc4388/rendezvous";
486
487 let server = MatrixMockServer::new().await;
488 let client = server.client_builder().logged_in_with_oauth().build().await;
489
490 {
491 let _discover_guard = server
492 .server()
493 .register_as_scoped(
494 Mock::given(method("GET"))
495 .and(path(URL))
496 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
497 "create_available": true,
498 })))
499 .expect(1),
500 )
501 .await;
502
503 let supported = client
504 .oauth()
505 .msc_4388_rendezvous_server_supported()
506 .await
507 .expect("We should be able to check if the rendezvous server is supported");
508
509 assert!(supported, "The rendezvous server should be supported");
510 }
511
512 {
513 let _discover_guard = server
514 .server()
515 .register_as_scoped(
516 Mock::given(method("GET"))
517 .and(path(URL))
518 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
519 "create_available": false,
520 })))
521 .expect(1),
522 )
523 .await;
524
525 let supported = client
526 .oauth()
527 .msc_4388_rendezvous_server_supported()
528 .await
529 .expect("We should be able to check if the rendezvous server is supported");
530
531 assert!(
532 !supported,
533 "The rendezvous server should not be supported, because create_available is false"
534 );
535 }
536
537 {
538 let _discover_guard = server
539 .server()
540 .register_as_scoped(
541 Mock::given(method("GET"))
542 .and(path(URL))
543 .respond_with(ResponseTemplate::new(404))
544 .expect(1),
545 )
546 .await;
547
548 let supported = client
549 .oauth()
550 .msc_4388_rendezvous_server_supported()
551 .await
552 .expect("We should be able to check if the rendezvous server is supported");
553
554 assert!(
555 !supported,
556 "The rendezvous server should not be supported if we receive a 404 response"
557 );
558 }
559
560 {
561 let _discover_guard = server
562 .server()
563 .register_as_scoped(
564 Mock::given(method("GET"))
565 .and(path(URL))
566 .respond_with(ResponseTemplate::new(403))
567 .expect(1),
568 )
569 .await;
570
571 let supported = client
572 .oauth()
573 .msc_4388_rendezvous_server_supported()
574 .await
575 .expect("We should be able to check if the rendezvous server is supported");
576
577 assert!(
578 !supported,
579 "The rendezvous server should not be supported if we receive a 403 response"
580 );
581 }
582
583 {
584 let _discover_guard = server
585 .server()
586 .register_as_scoped(
587 Mock::given(method("GET"))
588 .and(path(URL))
589 .respond_with(ResponseTemplate::new(500))
590 .expect(1),
591 )
592 .await;
593
594 client
595 .oauth()
596 .msc_4388_rendezvous_server_supported()
597 .await
598 .expect_err("We should return an error if the homeserver can't tell us if the endpoint is supported or not");
599 }
600 }
601}