Skip to content

Commit 5f54237

Browse files
Johennespoljar
authored andcommitted
feat(ffi): add bindings for logging in by generating a QR code on the new device
Signed-off-by: Johannes Marbach <[email protected]>
1 parent f78f179 commit 5f54237

File tree

5 files changed

+221
-42
lines changed

5 files changed

+221
-42
lines changed

bindings/matrix-sdk-ffi/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ All notable changes to this project will be documented in this file.
2323
([#5760](https://github.com/matrix-org/matrix-rust-sdk/pull/5760))
2424
- Add `Room::subscribe_to_send_queue_updates` to observe room send queue updates.
2525
([#5761](https://github.com/matrix-org/matrix-rust-sdk/pull/5761))
26+
- `Client::login_with_qr_code` now returns a handler that allows performing the flow with either the
27+
current device scanning or generating the QR code. Additionally, new errors `HumanQrLoginError::CheckCodeAlreadySent`
28+
and `HumanQrLoginError::CheckCodeCannotBeSent` were added.
29+
([#5786](https://github.com/matrix-org/matrix-rust-sdk/pull/5786))
2630

2731
### Features:
2832

bindings/matrix-sdk-ffi/src/client.rs

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ use crate::{
102102
encryption::Encryption,
103103
notification::NotificationClient,
104104
notification_settings::NotificationSettings,
105-
qr_code::{HumanQrLoginError, QrCodeData, QrLoginProgressListener},
105+
qr_code::LoginWithQrCodeHandler,
106106
room::{RoomHistoryVisibility, RoomInfoListener},
107107
room_directory_search::RoomDirectorySearch,
108108
room_preview::RoomPreview,
@@ -543,43 +543,17 @@ impl Client {
543543
Ok(())
544544
}
545545

546-
/// Log in using the provided [`QrCodeData`]. The `Client` must be built
547-
/// by providing [`QrCodeData::server_name`] as the server name for this
548-
/// login to succeed.
546+
/// Log in using a QR code.
549547
///
550-
/// This method uses the login mechanism described in [MSC4108]. As such
551-
/// this method requires OAuth 2.0 support as well as sliding sync support.
552-
///
553-
/// The usage of the progress_listener is required to transfer the
554-
/// [`CheckCode`] to the existing client.
548+
/// # Arguments
555549
///
556-
/// [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
557-
pub async fn login_with_qr_code(
550+
/// * `oidc_configuration` - The data to restore or register the client with
551+
/// the server.
552+
pub fn login_with_qr_code(
558553
self: Arc<Self>,
559-
qr_code_data: &QrCodeData,
560-
oidc_configuration: &OidcConfiguration,
561-
progress_listener: Box<dyn QrLoginProgressListener>,
562-
) -> Result<(), HumanQrLoginError> {
563-
let registration_data = oidc_configuration
564-
.registration_data()
565-
.map_err(|_| HumanQrLoginError::OidcMetadataInvalid)?;
566-
567-
let oauth = self.inner.oauth();
568-
let login = oauth.login_with_qr_code(Some(&registration_data)).scan(&qr_code_data.inner);
569-
570-
let mut progress = login.subscribe_to_progress();
571-
572-
// We create this task, which will get cancelled once it's dropped, just in case
573-
// the progress stream doesn't end.
574-
let _progress_task = TaskHandle::new(get_runtime_handle().spawn(async move {
575-
while let Some(state) = progress.next().await {
576-
progress_listener.on_update(state.into());
577-
}
578-
}));
579-
580-
login.await?;
581-
582-
Ok(())
554+
oidc_configuration: OidcConfiguration,
555+
) -> LoginWithQrCodeHandler {
556+
LoginWithQrCodeHandler::new(self.inner.oauth(), oidc_configuration)
583557
}
584558

585559
/// Restores the client from a `Session`.

bindings/matrix-sdk-ffi/src/qr_code.rs

Lines changed: 204 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,130 @@
11
use std::sync::Arc;
22

33
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,
610
},
711
crypto::types::qr_login::{LoginQrCodeDecodeError, QrCodeModeData},
812
};
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(&registration_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(&registration_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+
}
10128

11129
/// Data for the QR code login mechanism.
12130
///
@@ -71,6 +189,10 @@ pub enum HumanQrLoginError {
71189
OidcMetadataInvalid,
72190
#[error("The other device is not signed in and as such can't sign in other devices.")]
73191
OtherDeviceNotSignedIn,
192+
#[error("The check code was already sent.")]
193+
CheckCodeAlreadySent,
194+
#[error("The check code could not be sent.")]
195+
CheckCodeCannotBeSent,
74196
}
75197

76198
impl From<qrcode::QRCodeLoginError> for HumanQrLoginError {
@@ -122,7 +244,17 @@ impl From<qrcode::QRCodeLoginError> for HumanQrLoginError {
122244
}
123245
}
124246

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.
126258
#[derive(Debug, Default, Clone, uniffi::Enum)]
127259
pub enum QrLoginProgress {
128260
/// The login process is starting.
@@ -172,3 +304,71 @@ impl From<qrcode::LoginProgress<QrProgress>> for QrLoginProgress {
172304
}
173305
}
174306
}
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+
}

crates/matrix-sdk/src/authentication/oauth/qrcode/login.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,8 @@ pub enum GeneratedQrProgress {
289289
QrScanned(CheckCodeSender),
290290
}
291291

292-
/// Used to send the [`CheckCode`] to the new device that generated the
293-
/// QR code.
292+
/// Used to pass back the [`CheckCode`] entered by the user to verify that the
293+
/// secure channel is indeed secure.
294294
#[derive(Clone, Debug)]
295295
pub struct CheckCodeSender {
296296
inner: Arc<Mutex<Option<tokio::sync::oneshot::Sender<u8>>>>,

crates/matrix-sdk/src/authentication/oauth/qrcode/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ mod secure_channel;
4242

4343
pub use self::{
4444
login::{
45-
GeneratedQrProgress, LoginProgress, LoginWithGeneratedQrCode, LoginWithQrCode, QrProgress,
45+
CheckCodeSender, CheckCodeSenderError, GeneratedQrProgress, LoginProgress,
46+
LoginWithGeneratedQrCode, LoginWithQrCode, QrProgress,
4647
},
4748
messages::{LoginFailureReason, LoginProtocolType, QrAuthMessage},
4849
};

0 commit comments

Comments
 (0)