|
1 | 1 | use std::sync::Arc; |
2 | 2 |
|
3 | 3 | use matrix_sdk::{ |
4 | | - authentication::oauth::qrcode::{ |
5 | | - self, DeviceCodeErrorResponseType, LoginFailureReason, QrProgress, |
| 4 | + authentication::oauth::{ |
| 5 | + qrcode::{ |
| 6 | + self, CheckCodeSender as SdkCheckCodeSender, CheckCodeSenderError, |
| 7 | + DeviceCodeErrorResponseType, GeneratedQrProgress, LoginFailureReason, QrProgress, |
| 8 | + }, |
| 9 | + OAuth, |
6 | 10 | }, |
7 | 11 | crypto::types::qr_login::{LoginQrCodeDecodeError, QrCodeModeData}, |
8 | 12 | }; |
9 | | -use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm}; |
| 13 | +use matrix_sdk_common::{stream::StreamExt, SendOutsideWasm, SyncOutsideWasm}; |
| 14 | + |
| 15 | +use crate::{ |
| 16 | + authentication::OidcConfiguration, runtime::get_runtime_handle, task_handle::TaskHandle, |
| 17 | +}; |
| 18 | + |
| 19 | +/// Handler for logging in with a QR code. |
| 20 | +#[derive(uniffi::Object)] |
| 21 | +pub struct LoginWithQrCodeHandler { |
| 22 | + oauth: OAuth, |
| 23 | + oidc_configuration: OidcConfiguration, |
| 24 | +} |
| 25 | + |
| 26 | +impl LoginWithQrCodeHandler { |
| 27 | + pub(crate) fn new(oauth: OAuth, oidc_configuration: OidcConfiguration) -> Self { |
| 28 | + Self { oauth, oidc_configuration } |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +#[matrix_sdk_ffi_macros::export] |
| 33 | +impl LoginWithQrCodeHandler { |
| 34 | + /// This method allows you to log in with a scanned QR code. |
| 35 | + /// |
| 36 | + /// The existing device needs to display the QR code which this device can |
| 37 | + /// scan, call this method and handle its progress updates to log in. |
| 38 | + /// |
| 39 | + /// For the login to succeed, the [`Client`] associated with the |
| 40 | + /// [`LoginWithQrCodeHandler`] must have been built with |
| 41 | + /// [`QrCodeData::server_name`] as the server name. |
| 42 | + /// |
| 43 | + /// This method uses the login mechanism described in [MSC4108]. As such, |
| 44 | + /// it requires OAuth 2.0 support as well as Sliding Sync support. |
| 45 | + /// |
| 46 | + /// For the reverse flow where this device generates the QR code for the |
| 47 | + /// existing device to scan, use [`LoginWithQrCodeHandler::generate`]. |
| 48 | + /// |
| 49 | + /// # Arguments |
| 50 | + /// |
| 51 | + /// * `qr_code_data` - The [`QrCodeData`] scanned from the QR code. |
| 52 | + /// * `progress_listener` - A progress listener that must also be used to |
| 53 | + /// transfer the [`CheckCode`] to the existing device. |
| 54 | + /// |
| 55 | + /// [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108 |
| 56 | + pub async fn scan( |
| 57 | + self: Arc<Self>, |
| 58 | + qr_code_data: &QrCodeData, |
| 59 | + progress_listener: Box<dyn QrLoginProgressListener>, |
| 60 | + ) -> Result<(), HumanQrLoginError> { |
| 61 | + let registration_data = self |
| 62 | + .oidc_configuration |
| 63 | + .registration_data() |
| 64 | + .map_err(|_| HumanQrLoginError::OidcMetadataInvalid)?; |
| 65 | + |
| 66 | + let login = |
| 67 | + self.oauth.login_with_qr_code(Some(®istration_data)).scan(&qr_code_data.inner); |
| 68 | + |
| 69 | + let mut progress = login.subscribe_to_progress(); |
| 70 | + |
| 71 | + // We create this task, which will get cancelled once it's dropped, just in case |
| 72 | + // the progress stream doesn't end. |
| 73 | + let _progress_task = TaskHandle::new(get_runtime_handle().spawn(async move { |
| 74 | + while let Some(state) = progress.next().await { |
| 75 | + progress_listener.on_update(state.into()); |
| 76 | + } |
| 77 | + })); |
| 78 | + |
| 79 | + login.await?; |
| 80 | + |
| 81 | + Ok(()) |
| 82 | + } |
| 83 | + |
| 84 | + /// This method allows you to log in by generating a QR code. |
| 85 | + /// |
| 86 | + /// This device needs to call this method and handle its progress updates to |
| 87 | + /// generate a QR code which the existing device can scan and grant the |
| 88 | + /// log in. |
| 89 | + /// |
| 90 | + /// This method uses the login mechanism described in [MSC4108]. As such, |
| 91 | + /// it requires OAuth 2.0 support as well as Sliding Sync support. |
| 92 | + /// |
| 93 | + /// For the reverse flow where the existing device generates the QR code |
| 94 | + /// for this device to scan, use [`LoginWithQrCodeHandler::scan`]. |
| 95 | + /// |
| 96 | + /// # Arguments |
| 97 | + /// |
| 98 | + /// * `progress_listener` - A progress listener that must also be used to |
| 99 | + /// obtain the [`QrCodeData`] and collect the [`CheckCode`] from the user. |
| 100 | + /// |
| 101 | + /// [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108 |
| 102 | + pub async fn generate( |
| 103 | + self: Arc<Self>, |
| 104 | + progress_listener: Box<dyn GeneratedQrLoginProgressListener>, |
| 105 | + ) -> Result<(), HumanQrLoginError> { |
| 106 | + let registration_data = self |
| 107 | + .oidc_configuration |
| 108 | + .registration_data() |
| 109 | + .map_err(|_| HumanQrLoginError::OidcMetadataInvalid)?; |
| 110 | + |
| 111 | + let login = self.oauth.login_with_qr_code(Some(®istration_data)).generate(); |
| 112 | + |
| 113 | + let mut progress = login.subscribe_to_progress(); |
| 114 | + |
| 115 | + // We create this task, which will get cancelled once it's dropped, just in case |
| 116 | + // the progress stream doesn't end. |
| 117 | + let _progress_task = TaskHandle::new(get_runtime_handle().spawn(async move { |
| 118 | + while let Some(state) = progress.next().await { |
| 119 | + progress_listener.on_update(state.into()); |
| 120 | + } |
| 121 | + })); |
| 122 | + |
| 123 | + login.await?; |
| 124 | + |
| 125 | + Ok(()) |
| 126 | + } |
| 127 | +} |
10 | 128 |
|
11 | 129 | /// Data for the QR code login mechanism. |
12 | 130 | /// |
@@ -71,6 +189,10 @@ pub enum HumanQrLoginError { |
71 | 189 | OidcMetadataInvalid, |
72 | 190 | #[error("The other device is not signed in and as such can't sign in other devices.")] |
73 | 191 | OtherDeviceNotSignedIn, |
| 192 | + #[error("The check code was already sent.")] |
| 193 | + CheckCodeAlreadySent, |
| 194 | + #[error("The check code could not be sent.")] |
| 195 | + CheckCodeCannotBeSent, |
74 | 196 | } |
75 | 197 |
|
76 | 198 | impl From<qrcode::QRCodeLoginError> for HumanQrLoginError { |
@@ -122,7 +244,17 @@ impl From<qrcode::QRCodeLoginError> for HumanQrLoginError { |
122 | 244 | } |
123 | 245 | } |
124 | 246 |
|
125 | | -/// Enum describing the progress of the QR-code login. |
| 247 | +impl From<CheckCodeSenderError> for HumanQrLoginError { |
| 248 | + fn from(value: CheckCodeSenderError) -> Self { |
| 249 | + match value { |
| 250 | + CheckCodeSenderError::AlreadySent => HumanQrLoginError::CheckCodeAlreadySent, |
| 251 | + CheckCodeSenderError::CannotSend => HumanQrLoginError::CheckCodeCannotBeSent, |
| 252 | + } |
| 253 | + } |
| 254 | +} |
| 255 | + |
| 256 | +/// Enum describing the progress of logging in by scanning a QR code that was |
| 257 | +/// generated on an existing device. |
126 | 258 | #[derive(Debug, Default, Clone, uniffi::Enum)] |
127 | 259 | pub enum QrLoginProgress { |
128 | 260 | /// The login process is starting. |
@@ -172,3 +304,71 @@ impl From<qrcode::LoginProgress<QrProgress>> for QrLoginProgress { |
172 | 304 | } |
173 | 305 | } |
174 | 306 | } |
| 307 | + |
| 308 | +/// Enum describing the progress of logging in by generating a QR code and |
| 309 | +/// having an existing device scan it. |
| 310 | +#[derive(Debug, Default, Clone, uniffi::Enum)] |
| 311 | +pub enum GeneratedQrLoginProgress { |
| 312 | + /// The login process is starting. |
| 313 | + #[default] |
| 314 | + Starting, |
| 315 | + /// We have established the secure channel and now need to display the |
| 316 | + /// QR code so that the existing device can scan it. |
| 317 | + QrReady { qr_code: Arc<QrCodeData> }, |
| 318 | + /// The existing device has scanned the QR code and is displaying the |
| 319 | + /// checkcode. We now need to ask the user to enter the checkcode so that |
| 320 | + /// we can verify that the channel is indeed secure. |
| 321 | + QrScanned { check_code_sender: Arc<CheckCodeSender> }, |
| 322 | + /// We are waiting for the login and for the OAuth 2.0 authorization server |
| 323 | + /// to give us an access token. |
| 324 | + WaitingForToken { user_code: String }, |
| 325 | + /// We are syncing secrets. |
| 326 | + SyncingSecrets, |
| 327 | + /// The login has successfully finished. |
| 328 | + Done, |
| 329 | +} |
| 330 | + |
| 331 | +#[matrix_sdk_ffi_macros::export(callback_interface)] |
| 332 | +pub trait GeneratedQrLoginProgressListener: SyncOutsideWasm + SendOutsideWasm { |
| 333 | + fn on_update(&self, state: GeneratedQrLoginProgress); |
| 334 | +} |
| 335 | + |
| 336 | +impl From<qrcode::LoginProgress<GeneratedQrProgress>> for GeneratedQrLoginProgress { |
| 337 | + fn from(value: qrcode::LoginProgress<GeneratedQrProgress>) -> Self { |
| 338 | + use qrcode::LoginProgress; |
| 339 | + |
| 340 | + match value { |
| 341 | + LoginProgress::Starting => Self::Starting, |
| 342 | + LoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrReady(inner)) => { |
| 343 | + Self::QrReady { qr_code: Arc::new(QrCodeData { inner }) } |
| 344 | + } |
| 345 | + LoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrScanned(inner)) => { |
| 346 | + Self::QrScanned { check_code_sender: Arc::new(CheckCodeSender { inner }) } |
| 347 | + } |
| 348 | + LoginProgress::WaitingForToken { user_code } => Self::WaitingForToken { user_code }, |
| 349 | + LoginProgress::SyncingSecrets => Self::SyncingSecrets, |
| 350 | + LoginProgress::Done => Self::Done, |
| 351 | + } |
| 352 | + } |
| 353 | +} |
| 354 | + |
| 355 | +#[derive(Debug, uniffi::Object)] |
| 356 | +/// Used to pass back the [`CheckCode`] entered by the user to verify that the |
| 357 | +/// secure channel is indeed secure. |
| 358 | +pub struct CheckCodeSender { |
| 359 | + inner: SdkCheckCodeSender, |
| 360 | +} |
| 361 | + |
| 362 | +#[matrix_sdk_ffi_macros::export] |
| 363 | +impl CheckCodeSender { |
| 364 | + /// Send the [`CheckCode`]. |
| 365 | + /// |
| 366 | + /// Calling this method more than once will result in an error. |
| 367 | + /// |
| 368 | + /// # Arguments |
| 369 | + /// |
| 370 | + /// * `check_code` - The check code in digits representation. |
| 371 | + pub async fn send(&self, code: u8) -> Result<(), HumanQrLoginError> { |
| 372 | + self.inner.send(code).await.map_err(HumanQrLoginError::from) |
| 373 | + } |
| 374 | +} |
0 commit comments