matrix_sdk/authentication/oauth/qrcode/
grant.rs

1// Copyright 2025 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::time::Duration;
16
17use eyeball::SharedObservable;
18use futures_core::Stream;
19use matrix_sdk_base::{
20    boxed_into_future,
21    crypto::types::{
22        SecretsBundle,
23        qr_login::{QrCodeData, QrCodeMode},
24    },
25};
26use oauth2::VerificationUriComplete;
27use ruma::time::Instant;
28use url::Url;
29#[cfg(doc)]
30use vodozemac::ecies::CheckCode;
31
32use super::{
33    LoginProtocolType, QrAuthMessage,
34    secure_channel::{EstablishedSecureChannel, SecureChannel},
35};
36use crate::{
37    Client,
38    authentication::oauth::qrcode::{
39        CheckCodeSender, GeneratedQrProgress, LoginFailureReason, QRCodeGrantLoginError,
40        QrProgress, SecureChannelError,
41    },
42};
43
44async fn export_secrets_bundle(client: &Client) -> Result<SecretsBundle, QRCodeGrantLoginError> {
45    let secrets_bundle = client
46        .olm_machine()
47        .await
48        .as_ref()
49        .ok_or_else(|| QRCodeGrantLoginError::MissingSecretsBackup(None))?
50        .store()
51        .export_secrets_bundle()
52        .await?;
53    Ok(secrets_bundle)
54}
55
56async fn finish_login_grant<Q>(
57    client: &Client,
58    channel: &mut EstablishedSecureChannel,
59    device_creation_timeout: Duration,
60    secrets_bundle: &SecretsBundle,
61    state: &SharedObservable<GrantLoginProgress<Q>>,
62) -> Result<(), QRCodeGrantLoginError> {
63    // The new device registers with the authorization server and sends it a device
64    // authorization authorization request.
65    // -- MSC4108 OAuth 2.0 login step 2
66
67    // We wait for the new device to send us the m.login.protocol message with the
68    // device authorization grant information. -- MSC4108 OAuth 2.0 login step 3
69    let message = channel.receive_json().await?;
70    let QrAuthMessage::LoginProtocol { device_authorization_grant, protocol, device_id } = message
71    else {
72        return Err(QRCodeGrantLoginError::Unknown(
73            "Receiving unexpected message when expecting LoginProtocol".to_owned(),
74        ));
75    };
76
77    // We verify the selected protocol.
78    // -- MSC4108 OAuth 2.0 login step 4
79    if protocol != LoginProtocolType::DeviceAuthorizationGrant {
80        channel
81            .send_json(QrAuthMessage::LoginFailure {
82                reason: LoginFailureReason::UnsupportedProtocol,
83                homeserver: None,
84            })
85            .await?;
86        return Err(QRCodeGrantLoginError::UnsupportedProtocol(protocol));
87    }
88
89    // We check that the device ID is still available.
90    // -- MSC4108 OAuth 2.0 login step 4 continued
91    if !matches!(client.device_exists(device_id.to_base64().into()).await, Ok(false)) {
92        channel
93            .send_json(QrAuthMessage::LoginFailure {
94                reason: LoginFailureReason::DeviceAlreadyExists,
95                homeserver: None,
96            })
97            .await?;
98        return Err(QRCodeGrantLoginError::DeviceIDAlreadyInUse);
99    }
100
101    // We emit an update so that the caller can open the verification URI in a
102    // system browser to consent to the login.
103    // -- MSC4108 OAuth 2.0 login step 4 continued
104    let verification_uri = Url::parse(
105        device_authorization_grant
106            .verification_uri_complete
107            .map(VerificationUriComplete::into_secret)
108            .unwrap_or(device_authorization_grant.verification_uri.to_string())
109            .as_str(),
110    )
111    .map_err(|_| QRCodeGrantLoginError::UnableToCreateDevice)?;
112    state.set(GrantLoginProgress::WaitingForAuth { verification_uri });
113
114    // We send the new device the m.login.protocol_accepted message to let it know
115    // that the consent process is in progress.
116    // -- MSC4108 OAuth 2.0 login step 4 continued
117    let message = QrAuthMessage::LoginProtocolAccepted;
118    channel.send_json(&message).await?;
119
120    // The new device displays the user code it received from the authorization
121    // server and starts polling for an access token. In parallel, the user
122    // consents to the new login in the browser on this device, while verifying
123    // the user code displayed on the other device. -- MSC4108 OAuth 2.0 login
124    // steps 5 & 6
125
126    // We wait for the new device to send us the m.login.success message
127    let message: QrAuthMessage = channel.receive_json().await?;
128    let QrAuthMessage::LoginSuccess = message else {
129        return Err(QRCodeGrantLoginError::Unknown(
130            "Receiving unexpected message when expecting LoginSuccess".to_owned(),
131        ));
132    };
133
134    // We check that the new device was created successfully, allowing for the
135    // specified delay. -- MSC4108 Secret sharing and device verification step 1
136    let deadline = Instant::now() + device_creation_timeout;
137
138    loop {
139        if matches!(client.device_exists(device_id.to_base64().into()).await, Ok(true)) {
140            break;
141        } else {
142            // If the deadline hasn't yet passed, give it some time and retry the request.
143            if Instant::now() < deadline {
144                tokio::time::sleep(Duration::from_millis(500)).await;
145                continue;
146            } else {
147                // The deadline has passed. Let's fail the login process.
148                channel
149                    .send_json(QrAuthMessage::LoginFailure {
150                        reason: LoginFailureReason::DeviceNotFound,
151                        homeserver: None,
152                    })
153                    .await?;
154                return Err(QRCodeGrantLoginError::DeviceIDAlreadyInUse);
155            }
156        }
157    }
158
159    // We send the new device the secrets bundle.
160    // -- MSC4108 Secret sharing and device verification step 2
161    state.set(GrantLoginProgress::SyncingSecrets);
162    let message = QrAuthMessage::LoginSecrets(secrets_bundle.clone());
163    channel.send_json(&message).await?;
164
165    // And we're done.
166    state.set(GrantLoginProgress::Done);
167
168    Ok(())
169}
170
171/// The progress of granting the login.
172#[derive(Clone, Debug, Default)]
173pub enum GrantLoginProgress<Q> {
174    /// We're just starting up, this is the default and initial state.
175    #[default]
176    Starting,
177    /// The secure channel is being established by exchanging the QR code
178    /// and/or [`CheckCode`].
179    EstablishingSecureChannel(Q),
180    /// The secure channel has been confirmed using the [`CheckCode`] and this
181    /// device is waiting for the authorization to complete.
182    WaitingForAuth {
183        /// A URI to open in a (secure) system browser to verify the new login.
184        verification_uri: Url,
185    },
186    /// The new device has been granted access and this device is sending the
187    /// secrets to it.
188    SyncingSecrets,
189    /// The process is complete.
190    Done,
191}
192
193/// Named future for granting login by scanning a QR code on this, existing,
194/// device that was generated by the other, new, device.
195#[derive(Debug)]
196pub struct GrantLoginWithScannedQrCode<'a> {
197    client: &'a Client,
198    qr_code_data: &'a QrCodeData,
199    device_creation_timeout: Duration,
200    state: SharedObservable<GrantLoginProgress<QrProgress>>,
201}
202
203impl<'a> GrantLoginWithScannedQrCode<'a> {
204    pub(crate) fn new(
205        client: &'a Client,
206        qr_code_data: &'a QrCodeData,
207        device_creation_timeout: Duration,
208    ) -> GrantLoginWithScannedQrCode<'a> {
209        GrantLoginWithScannedQrCode {
210            client,
211            qr_code_data,
212            device_creation_timeout,
213            state: Default::default(),
214        }
215    }
216}
217
218impl GrantLoginWithScannedQrCode<'_> {
219    /// Subscribe to the progress of QR code login.
220    ///
221    /// It's necessary to subscribe to this to capture the [`CheckCode`] in
222    /// order to display it to the other device and to obtain the
223    /// verification URL for consenting to the login.
224    pub fn subscribe_to_progress(
225        &self,
226    ) -> impl Stream<Item = GrantLoginProgress<QrProgress>> + use<> {
227        self.state.subscribe()
228    }
229}
230
231impl<'a> IntoFuture for GrantLoginWithScannedQrCode<'a> {
232    type Output = Result<(), QRCodeGrantLoginError>;
233    boxed_into_future!(extra_bounds: 'a);
234
235    fn into_future(self) -> Self::IntoFuture {
236        Box::pin(async move {
237            // Before we get here, the other device has created a new rendezvous session
238            // and presented a QR code which this device has scanned.
239            // -- MSC4108 Secure channel setup steps 1-3
240
241            // First things first, establish the secure channel. Since we're the one that
242            // scanned the QR code, we're certain that the secure channel is
243            // secure, under the assumption that we didn't scan the wrong QR code.
244            // -- MSC4108 Secure channel setup steps 3-5
245            let mut channel = EstablishedSecureChannel::from_qr_code(
246                self.client.inner.http_client.inner.clone(),
247                self.qr_code_data,
248                QrCodeMode::Reciprocate,
249            )
250            .await?;
251
252            // The other side isn't yet sure that it's talking to the right device, show
253            // a check code so they can confirm.
254            // -- MSC4108 Secure channel setup step 6
255            let check_code = channel.check_code().to_owned();
256            self.state
257                .set(GrantLoginProgress::EstablishingSecureChannel(QrProgress { check_code }));
258
259            // The user now enters the checkcode on the other device which verifies it
260            // and will only continue requesting the login if the code matches.
261            // -- MSC4108 Secure channel setup step 7
262
263            // Inform the other device about the available login protocols and the
264            // homeserver to use.
265            // -- MSC4108 OAuth 2.0 login step 1
266            let message = QrAuthMessage::LoginProtocols {
267                protocols: vec![LoginProtocolType::DeviceAuthorizationGrant],
268                homeserver: self.client.homeserver(),
269            };
270            channel.send_json(message).await?;
271
272            // Proceed with granting the login.
273            // -- MSC4108 OAuth 2.0 login remaining steps
274            finish_login_grant(
275                self.client,
276                &mut channel,
277                self.device_creation_timeout,
278                &export_secrets_bundle(self.client).await?,
279                &self.state,
280            )
281            .await
282        })
283    }
284}
285
286/// Named future for granting login by generating a QR code on this, existing,
287/// device to be scanned by the other, new, device.
288#[derive(Debug)]
289pub struct GrantLoginWithGeneratedQrCode<'a> {
290    client: &'a Client,
291    device_creation_timeout: Duration,
292    state: SharedObservable<GrantLoginProgress<GeneratedQrProgress>>,
293}
294
295impl<'a> GrantLoginWithGeneratedQrCode<'a> {
296    pub(crate) fn new(
297        client: &'a Client,
298        device_creation_timeout: Duration,
299    ) -> GrantLoginWithGeneratedQrCode<'a> {
300        GrantLoginWithGeneratedQrCode { client, device_creation_timeout, state: Default::default() }
301    }
302}
303
304impl GrantLoginWithGeneratedQrCode<'_> {
305    /// Subscribe to the progress of QR code login.
306    ///
307    /// It's necessary to subscribe to this to capture the QR code in order to
308    /// display it to the other device, to feed the [`CheckCode`] entered by the
309    /// user back in and to obtain the verification URL for consenting to
310    /// the login.
311    pub fn subscribe_to_progress(
312        &self,
313    ) -> impl Stream<Item = GrantLoginProgress<GeneratedQrProgress>> + use<> {
314        self.state.subscribe()
315    }
316}
317
318impl<'a> IntoFuture for GrantLoginWithGeneratedQrCode<'a> {
319    type Output = Result<(), QRCodeGrantLoginError>;
320    boxed_into_future!(extra_bounds: 'a);
321
322    fn into_future(self) -> Self::IntoFuture {
323        Box::pin(async move {
324            // Create a new ephemeral key pair and a rendezvous session to grant a
325            // login with.
326            // -- MSC4108 Secure channel setup steps 1 & 2
327            let homeserver_url = self.client.homeserver();
328            let http_client = self.client.inner.http_client.clone();
329            let channel = SecureChannel::reciprocate(http_client, &homeserver_url).await?;
330
331            // Extract the QR code data and emit an update so that the caller can
332            // present the QR code for scanning by the new device.
333            // -- MSC4108 Secure channel setup step 3
334            self.state.set(GrantLoginProgress::EstablishingSecureChannel(
335                GeneratedQrProgress::QrReady(channel.qr_code_data().clone()),
336            ));
337
338            // Wait for the secure channel to connect. The other device now needs to scan
339            // the QR code and send us the LoginInitiateMessage which we respond to
340            // with the LoginOkMessage. -- MSC4108 step 4 & 5
341            let channel = channel.connect().await?;
342
343            // The other device now needs to verify our message, compute the checkcode and
344            // display it. We emit a progress update to let the caller prompt the
345            // user to enter the checkcode and feed it back to us.
346            // -- MSC4108 Secure channel setup step 6
347            let (tx, rx) = tokio::sync::oneshot::channel();
348            self.state.set(GrantLoginProgress::EstablishingSecureChannel(
349                GeneratedQrProgress::QrScanned(CheckCodeSender::new(tx)),
350            ));
351            let check_code = rx.await.map_err(|_| SecureChannelError::CannotReceiveCheckCode)?;
352
353            // Use the checkcode to verify that the channel is actually secure.
354            // -- MSC4108 Secure channel setup step 7
355            let mut channel = channel.confirm(check_code)?;
356
357            // Since the QR code was generated on this existing device, the new device can
358            // derive the homeserver to use for logging in from the QR code and we
359            // don't need to send the m.login.protocols message.
360            // -- MSC4108 OAuth 2.0 login step 1
361
362            // Proceed with granting the login.
363            // -- MSC4108 OAuth 2.0 login remaining steps
364            finish_login_grant(
365                self.client,
366                &mut channel,
367                self.device_creation_timeout,
368                &export_secrets_bundle(self.client).await?,
369                &self.state,
370            )
371            .await
372        })
373    }
374}
375
376#[cfg(all(test, not(target_family = "wasm")))]
377mod test {
378    use assert_matches2::{assert_let, assert_matches};
379    use futures_util::StreamExt;
380    use matrix_sdk_base::crypto::types::SecretsBundle;
381    use matrix_sdk_common::executor::spawn;
382    use matrix_sdk_test::async_test;
383    use oauth2::{EndUserVerificationUrl, VerificationUriComplete};
384    use ruma::{owned_device_id, owned_user_id};
385    use tokio::sync::oneshot;
386    use tracing::debug;
387    use vodozemac::Curve25519PublicKey;
388
389    use super::*;
390    use crate::{
391        authentication::oauth::qrcode::{
392            LoginFailureReason, QrAuthMessage,
393            messages::{AuthorizationGrant, LoginProtocolType},
394            secure_channel::{EstablishedSecureChannel, test::MockedRendezvousServer},
395        },
396        http_client::HttpClient,
397        test_utils::mocks::MatrixMockServer,
398    };
399
400    enum BobBehaviour {
401        HappyPath,
402        UnexpectedMessageInsteadOfLoginProtocol,
403        DeviceAlreadyExists,
404        DeviceNotCreated,
405    }
406
407    #[allow(clippy::too_many_arguments)]
408    async fn request_login_with_scanned_qr_code(
409        behaviour: BobBehaviour,
410        qr_code_rx: oneshot::Receiver<QrCodeData>,
411        check_code_tx: oneshot::Sender<u8>,
412        server: MatrixMockServer,
413        // The rendezvous server is here because it contains MockGuards that are tied to the
414        // lifetime of the MatrixMockServer. Otherwise we might attempt to drop the
415        // MatrixMockServer before the MockGuards.
416        _rendezvous_server: MockedRendezvousServer,
417        device_authorization_grant: Option<AuthorizationGrant>,
418        secrets_bundle: Option<SecretsBundle>,
419    ) {
420        // Wait for Alice to produce the qr code.
421        let qr_code_data = qr_code_rx.await.expect("Bob should receive the QR code");
422
423        // Use the QR code to establish the secure channel from the new client (Bob).
424        let mut bob = EstablishedSecureChannel::from_qr_code(
425            reqwest::Client::new(),
426            &qr_code_data,
427            QrCodeMode::Login,
428        )
429        .await
430        .expect("Bob should be able to connect the secure channel");
431
432        // Let Alice know about the checkcode so she can verify the channel.
433        check_code_tx
434            .send(bob.check_code().to_digit())
435            .expect("Bob should be able to send the checkcode");
436
437        match behaviour {
438            BobBehaviour::UnexpectedMessageInsteadOfLoginProtocol => {
439                // Send an unexpected message and exit.
440                let message = QrAuthMessage::LoginSuccess;
441                bob.send_json(message).await.unwrap();
442                return;
443            }
444            BobBehaviour::DeviceAlreadyExists => {
445                // Mock the endpoint for querying devices so that Alice thinks the device
446                // already exists.
447                server.mock_get_device().ok().expect(1..).named("get_device").mount().await;
448
449                // Now send the LoginProtocol message.
450                let message = QrAuthMessage::LoginProtocol {
451                    protocol: LoginProtocolType::DeviceAuthorizationGrant,
452                    device_authorization_grant: device_authorization_grant
453                        .expect("Bob needs the device authorization grant"),
454                    device_id: Curve25519PublicKey::from_base64(
455                        "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
456                    )
457                    .unwrap(),
458                };
459                bob.send_json(message).await.unwrap();
460
461                // Alice should fail the login with the appropriate reason.
462                let message = bob
463                    .receive_json()
464                    .await
465                    .expect("Bob should receive the LoginFailure message from Alice");
466                assert_let!(QrAuthMessage::LoginFailure { reason, .. } = message);
467                assert_matches!(reason, LoginFailureReason::DeviceAlreadyExists);
468
469                return; // Exit.
470            }
471            _ => {
472                // Send the LoginProtocol message.
473                let message = QrAuthMessage::LoginProtocol {
474                    protocol: LoginProtocolType::DeviceAuthorizationGrant,
475                    device_authorization_grant: device_authorization_grant
476                        .expect("Bob needs the device authorization grant"),
477                    device_id: Curve25519PublicKey::from_base64(
478                        "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
479                    )
480                    .unwrap(),
481                };
482                bob.send_json(message).await.unwrap();
483            }
484        }
485
486        // Receive the LoginProtocolAccepted message.
487        let message = bob
488            .receive_json()
489            .await
490            .expect("Bob should receive the LoginProtocolAccepted message from Alice");
491        assert_let!(QrAuthMessage::LoginProtocolAccepted = message);
492
493        match behaviour {
494            BobBehaviour::DeviceNotCreated => {
495                // Don't mock the endpoint for querying devices so that Alice cannot verify that
496                // we have logged in.
497
498                // Send the LoginSuccess message to claim that we have logged in.
499                let message = QrAuthMessage::LoginSuccess;
500                bob.send_json(message).await.unwrap();
501
502                // Alice should eventually give up querying our device and fail the login with
503                // the appropriate reason.
504                let message = bob
505                    .receive_json()
506                    .await
507                    .expect("Bob should receive the LoginFailure message from Alice");
508                assert_let!(QrAuthMessage::LoginFailure { reason, .. } = message);
509                assert_matches!(reason, LoginFailureReason::DeviceNotFound);
510
511                return; // Exit.
512            }
513            _ => {
514                // Mock the endpoint for querying devices so that Alice thinks we have logged
515                // in.
516                server.mock_get_device().ok().expect(1..).named("get_device").mount().await;
517
518                // Send the LoginSuccess message.
519                let message = QrAuthMessage::LoginSuccess;
520                bob.send_json(message).await.unwrap();
521            }
522        }
523
524        // Receive the LoginSecrets message.
525        let message = bob
526            .receive_json()
527            .await
528            .expect("Bob should receive the LoginSecrets message from Alice");
529        assert_let!(QrAuthMessage::LoginSecrets(bundle) = message);
530
531        // Verify that we received the correct secrets.
532        assert_eq!(
533            serde_json::to_value(&secrets_bundle).unwrap(),
534            serde_json::to_value(&bundle).unwrap()
535        );
536    }
537
538    #[allow(clippy::too_many_arguments)]
539    async fn request_login_with_generated_qr_code(
540        behaviour: BobBehaviour,
541        channel: SecureChannel,
542        check_code_rx: oneshot::Receiver<u8>,
543        server: MatrixMockServer,
544        // The rendezvous server is here because it contains MockGuards that are tied to the
545        // lifetime of the MatrixMockServer. Otherwise we might attempt to drop the
546        // MatrixMockServer before the MockGuards.
547        _rendezvous_server: MockedRendezvousServer,
548        homeserver: Url,
549        device_authorization_grant: Option<AuthorizationGrant>,
550        secrets_bundle: Option<SecretsBundle>,
551    ) {
552        // Wait for Alice to scan the qr code and connect the secure channel.
553        let channel =
554            channel.connect().await.expect("Bob should be able to connect the secure channel");
555
556        // Wait for Alice to send us the checkcode and use it to verify the channel.
557        let check_code = check_code_rx.await.expect("Bob should receive the checkcode");
558        let mut bob = channel
559            .confirm(check_code)
560            .expect("Bob should be able to confirm the channel is secure");
561
562        // Receive the LoginProtocols message.
563        let message = bob
564            .receive_json()
565            .await
566            .expect("Bob should receive the LoginProtocolAccepted message from Alice");
567        assert_let!(
568            QrAuthMessage::LoginProtocols { protocols, homeserver: alice_homeserver } = message
569        );
570        assert_eq!(protocols, vec![LoginProtocolType::DeviceAuthorizationGrant]);
571        assert_eq!(alice_homeserver, homeserver);
572
573        match behaviour {
574            BobBehaviour::UnexpectedMessageInsteadOfLoginProtocol => {
575                // Send an unexpected message and exit.
576                let message = QrAuthMessage::LoginSuccess;
577                bob.send_json(message).await.unwrap();
578                return;
579            }
580            BobBehaviour::DeviceAlreadyExists => {
581                // Mock the endpoint for querying devices so that Alice thinks the device
582                // already exists.
583                server.mock_get_device().ok().expect(1..).named("get_device").mount().await;
584
585                // Now send the LoginProtocol message.
586                let message = QrAuthMessage::LoginProtocol {
587                    protocol: LoginProtocolType::DeviceAuthorizationGrant,
588                    device_authorization_grant: device_authorization_grant
589                        .expect("Bob needs the device authorization grant"),
590                    device_id: Curve25519PublicKey::from_base64(
591                        "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
592                    )
593                    .unwrap(),
594                };
595                bob.send_json(message).await.unwrap();
596
597                // Alice should fail the login with the appropriate reason.
598                let message = bob
599                    .receive_json()
600                    .await
601                    .expect("Bob should receive the LoginFailure message from Alice");
602                assert_let!(QrAuthMessage::LoginFailure { reason, .. } = message);
603                assert_matches!(reason, LoginFailureReason::DeviceAlreadyExists);
604
605                return; // Exit.
606            }
607            _ => {
608                // Send the LoginProtocol message.
609                let message = QrAuthMessage::LoginProtocol {
610                    protocol: LoginProtocolType::DeviceAuthorizationGrant,
611                    device_authorization_grant: device_authorization_grant
612                        .expect("Bob needs the device authorization grant"),
613                    device_id: Curve25519PublicKey::from_base64(
614                        "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
615                    )
616                    .unwrap(),
617                };
618                bob.send_json(message).await.unwrap();
619            }
620        }
621
622        // Receive the LoginProtocolAccepted message.
623        let message = bob
624            .receive_json()
625            .await
626            .expect("Bob should receive the LoginProtocolAccepted message from Alice");
627        assert_let!(QrAuthMessage::LoginProtocolAccepted = message);
628
629        match behaviour {
630            BobBehaviour::DeviceNotCreated => {
631                // Don't mock the endpoint for querying devices so that Alice cannot verify that
632                // we have logged in.
633
634                // Send the LoginSuccess message to claim that we have logged in.
635                let message = QrAuthMessage::LoginSuccess;
636                bob.send_json(message).await.unwrap();
637
638                // Alice should eventually give up querying our device and fail the login with
639                // the appropriate reason.
640                let message = bob
641                    .receive_json()
642                    .await
643                    .expect("Bob should receive the LoginFailure message from Alice");
644                assert_let!(QrAuthMessage::LoginFailure { reason, .. } = message);
645                assert_matches!(reason, LoginFailureReason::DeviceNotFound);
646
647                return; // Exit.
648            }
649            _ => {
650                // Mock the endpoint for querying devices so that Alice thinks we have logged
651                // in.
652                server.mock_get_device().ok().expect(1..).named("get_device").mount().await;
653
654                // Send the LoginSuccess message.
655                let message = QrAuthMessage::LoginSuccess;
656                bob.send_json(message).await.unwrap();
657            }
658        }
659
660        // Receive the LoginSecrets message.
661        let message = bob
662            .receive_json()
663            .await
664            .expect("Bob should receive the LoginSecrets message from Alice");
665        assert_let!(QrAuthMessage::LoginSecrets(bundle) = message);
666
667        // Verify that we received the correct secrets.
668        assert_eq!(
669            serde_json::to_value(&secrets_bundle).unwrap(),
670            serde_json::to_value(&bundle).unwrap()
671        );
672    }
673
674    #[async_test]
675    async fn test_grant_login_with_generated_qr_code() {
676        let server = MatrixMockServer::new().await;
677        let rendezvous_server =
678            MockedRendezvousServer::new(server.server(), "abcdEFG12345", Duration::MAX).await;
679        debug!("Set up rendezvous server mock at {}", rendezvous_server.rendezvous_url);
680
681        let device_authorization_grant = AuthorizationGrant {
682            verification_uri_complete: Some(VerificationUriComplete::new(
683                "https://id.matrix.org/device/abcde".to_owned(),
684            )),
685            verification_uri: EndUserVerificationUrl::new(
686                "https://id.matrix.org/device/abcde?code=ABCDE".to_owned(),
687            )
688            .unwrap(),
689        };
690
691        server.mock_upload_keys().ok().expect(1).named("upload_keys").mount().await;
692        server
693            .mock_upload_cross_signing_keys()
694            .ok()
695            .expect(1)
696            .named("upload_xsigning_keys")
697            .mount()
698            .await;
699        server
700            .mock_upload_cross_signing_signatures()
701            .ok()
702            .expect(1)
703            .named("upload_xsigning_signatures")
704            .mount()
705            .await;
706
707        // Create the existing client (Alice).
708        let user_id = owned_user_id!("@alice:example.org");
709        let device_id = owned_device_id!("ALICE_DEVICE");
710        let alice = server
711            .client_builder_for_crypto_end_to_end(&user_id, &device_id)
712            .logged_in_with_oauth()
713            .build()
714            .await;
715        alice
716            .encryption()
717            .bootstrap_cross_signing(None)
718            .await
719            .expect("Alice should be able to set up cross signing");
720
721        // Prepare the login granting future.
722        let oauth = alice.oauth();
723        let grant = oauth
724            .grant_login_with_qr_code()
725            .device_creation_timeout(Duration::from_secs(2))
726            .generate();
727        let secrets_bundle = export_secrets_bundle(&alice)
728            .await
729            .expect("Alice should be able to export the secrets bundle");
730        let (qr_code_tx, qr_code_rx) = oneshot::channel();
731        let (checkcode_tx, checkcode_rx) = oneshot::channel();
732
733        // Spawn the updates task.
734        let mut updates = grant.subscribe_to_progress();
735        let mut state = grant.state.get();
736        let verification_uri_complete =
737            device_authorization_grant.clone().verification_uri_complete.unwrap().into_secret();
738        assert_matches!(state.clone(), GrantLoginProgress::Starting);
739        let updates_task = spawn(async move {
740            let mut qr_code_tx = Some(qr_code_tx);
741            let mut checkcode_rx = Some(checkcode_rx);
742
743            while let Some(update) = updates.next().await {
744                match &update {
745                    GrantLoginProgress::Starting => {
746                        assert_matches!(state, GrantLoginProgress::Starting);
747                    }
748                    GrantLoginProgress::EstablishingSecureChannel(
749                        GeneratedQrProgress::QrReady(qr_code_data),
750                    ) => {
751                        assert_matches!(state, GrantLoginProgress::Starting);
752                        qr_code_tx
753                            .take()
754                            .expect("The QR code should only be forwarded once")
755                            .send(qr_code_data.clone())
756                            .expect("Alice should be able to forward the QR code");
757                    }
758                    GrantLoginProgress::EstablishingSecureChannel(
759                        GeneratedQrProgress::QrScanned(checkcode_sender),
760                    ) => {
761                        assert_matches!(
762                            state,
763                            GrantLoginProgress::EstablishingSecureChannel(
764                                GeneratedQrProgress::QrReady(_)
765                            )
766                        );
767                        let checkcode = checkcode_rx
768                            .take()
769                            .expect("The checkcode should only be forwarded once")
770                            .await
771                            .expect("Alice should receive the checkcode");
772                        checkcode_sender
773                            .send(checkcode)
774                            .await
775                            .expect("Alice should be able to forward the checkcode");
776                    }
777                    GrantLoginProgress::WaitingForAuth { verification_uri } => {
778                        assert_matches!(
779                            state,
780                            GrantLoginProgress::EstablishingSecureChannel(
781                                GeneratedQrProgress::QrScanned(_)
782                            )
783                        );
784                        assert_eq!(verification_uri.as_str(), verification_uri_complete);
785                    }
786                    GrantLoginProgress::SyncingSecrets => {
787                        assert_matches!(state, GrantLoginProgress::WaitingForAuth { .. });
788                    }
789                    GrantLoginProgress::Done => {
790                        assert_matches!(state, GrantLoginProgress::SyncingSecrets);
791                        break;
792                    }
793                }
794                state = update;
795            }
796        });
797
798        // Let Bob request the login and run through the process.
799        let bob_task = spawn(async move {
800            request_login_with_scanned_qr_code(
801                BobBehaviour::HappyPath,
802                qr_code_rx,
803                checkcode_tx,
804                server,
805                rendezvous_server,
806                Some(device_authorization_grant),
807                Some(secrets_bundle),
808            )
809            .await;
810        });
811
812        // Wait for all tasks to finish.
813        grant.await.expect("Alice should be able to grant the login");
814        updates_task.await.expect("Alice should run through all progress states");
815        bob_task.await.expect("Bob's task should finish");
816    }
817
818    #[async_test]
819    async fn test_grant_login_with_scanned_qr_code() {
820        let server = MatrixMockServer::new().await;
821        let rendezvous_server =
822            MockedRendezvousServer::new(server.server(), "abcdEFG12345", Duration::MAX).await;
823        debug!("Set up rendezvous server mock at {}", rendezvous_server.rendezvous_url);
824
825        let device_authorization_grant = AuthorizationGrant {
826            verification_uri_complete: Some(VerificationUriComplete::new(
827                "https://id.matrix.org/device/abcde".to_owned(),
828            )),
829            verification_uri: EndUserVerificationUrl::new(
830                "https://id.matrix.org/device/abcde?code=ABCDE".to_owned(),
831            )
832            .unwrap(),
833        };
834
835        server.mock_upload_keys().ok().expect(1).named("upload_keys").mount().await;
836        server
837            .mock_upload_cross_signing_keys()
838            .ok()
839            .expect(1)
840            .named("upload_xsigning_keys")
841            .mount()
842            .await;
843        server
844            .mock_upload_cross_signing_signatures()
845            .ok()
846            .expect(1)
847            .named("upload_xsigning_signatures")
848            .mount()
849            .await;
850
851        // Create a secure channel on the new client (Bob) and extract the QR code.
852        let client = HttpClient::new(reqwest::Client::new(), Default::default());
853        let channel = SecureChannel::login(client, &rendezvous_server.homeserver_url)
854            .await
855            .expect("Bob should be able to create a secure channel.");
856        let qr_code_data = channel.qr_code_data().clone();
857
858        // Create the existing client (Alice).
859        let user_id = owned_user_id!("@alice:example.org");
860        let device_id = owned_device_id!("ALICE_DEVICE");
861        let alice = server
862            .client_builder_for_crypto_end_to_end(&user_id, &device_id)
863            .logged_in_with_oauth()
864            .build()
865            .await;
866        alice
867            .encryption()
868            .bootstrap_cross_signing(None)
869            .await
870            .expect("Alice should be able to set up cross signing");
871
872        // Prepare the login granting future using the QR code.
873        let oauth = alice.oauth();
874        let grant = oauth
875            .grant_login_with_qr_code()
876            .device_creation_timeout(Duration::from_secs(2))
877            .scan(&qr_code_data);
878        let secrets_bundle = export_secrets_bundle(&alice)
879            .await
880            .expect("Alice should be able to export the secrets bundle");
881        let (checkcode_tx, checkcode_rx) = oneshot::channel();
882
883        // Spawn the updates task.
884        let mut updates = grant.subscribe_to_progress();
885        let mut state = grant.state.get();
886        let verification_uri_complete =
887            device_authorization_grant.clone().verification_uri_complete.unwrap().into_secret();
888        assert_matches!(state.clone(), GrantLoginProgress::Starting);
889        let updates_task = spawn(async move {
890            let mut checkcode_tx = Some(checkcode_tx);
891
892            while let Some(update) = updates.next().await {
893                match &update {
894                    GrantLoginProgress::Starting => {
895                        assert_matches!(state, GrantLoginProgress::Starting);
896                    }
897                    GrantLoginProgress::EstablishingSecureChannel(QrProgress { check_code }) => {
898                        assert_matches!(state, GrantLoginProgress::Starting);
899                        checkcode_tx
900                            .take()
901                            .expect("The checkcode should only be forwarded once")
902                            .send(check_code.to_digit())
903                            .expect("Alice should be able to forward the checkcode");
904                    }
905                    GrantLoginProgress::WaitingForAuth { verification_uri } => {
906                        assert_matches!(
907                            state,
908                            GrantLoginProgress::EstablishingSecureChannel(QrProgress { .. })
909                        );
910                        assert_eq!(verification_uri.as_str(), verification_uri_complete);
911                    }
912                    GrantLoginProgress::SyncingSecrets => {
913                        assert_matches!(state, GrantLoginProgress::WaitingForAuth { .. });
914                    }
915                    GrantLoginProgress::Done => {
916                        assert_matches!(state, GrantLoginProgress::SyncingSecrets);
917                        break;
918                    }
919                }
920                state = update;
921            }
922        });
923
924        // Let Bob request the login and run through the process.
925        let bob_task = spawn(async move {
926            request_login_with_generated_qr_code(
927                BobBehaviour::HappyPath,
928                channel,
929                checkcode_rx,
930                server,
931                rendezvous_server,
932                alice.homeserver(),
933                Some(device_authorization_grant),
934                Some(secrets_bundle),
935            )
936            .await;
937        });
938
939        // Wait for all tasks to finish.
940        grant.await.expect("Alice should be able to grant the login");
941        updates_task.await.expect("Alice should run through all progress states");
942        bob_task.await.expect("Bob's task should finish");
943    }
944
945    #[async_test]
946    async fn test_grant_login_with_scanned_qr_code_with_homeserver_swap() {
947        let server = MatrixMockServer::new().await;
948        let rendezvous_server =
949            MockedRendezvousServer::new(server.server(), "abcdEFG12345", Duration::MAX).await;
950        debug!("Set up rendezvous server mock at {}", rendezvous_server.rendezvous_url);
951
952        let device_authorization_grant = AuthorizationGrant {
953            verification_uri_complete: Some(VerificationUriComplete::new(
954                "https://id.matrix.org/device/abcde".to_owned(),
955            )),
956            verification_uri: EndUserVerificationUrl::new(
957                "https://id.matrix.org/device/abcde?code=ABCDE".to_owned(),
958            )
959            .unwrap(),
960        };
961
962        let login_server = MatrixMockServer::new().await;
963
964        login_server.mock_upload_keys().ok().expect(1).named("upload_keys").mount().await;
965        login_server
966            .mock_upload_cross_signing_keys()
967            .ok()
968            .expect(1)
969            .named("upload_xsigning_keys")
970            .mount()
971            .await;
972        login_server
973            .mock_upload_cross_signing_signatures()
974            .ok()
975            .expect(1)
976            .named("upload_xsigning_signatures")
977            .mount()
978            .await;
979
980        // Create a secure channel on the new client (Bob) and extract the QR code.
981        let client = HttpClient::new(reqwest::Client::new(), Default::default());
982        let channel = SecureChannel::login(client, &rendezvous_server.homeserver_url)
983            .await
984            .expect("Bob should be able to create a secure channel.");
985        let qr_code_data = channel.qr_code_data().clone();
986
987        // Create the existing client (Alice).
988        let user_id = owned_user_id!("@alice:example.org");
989        let device_id = owned_device_id!("ALICE_DEVICE");
990        let alice = login_server
991            .client_builder_for_crypto_end_to_end(&user_id, &device_id)
992            .logged_in_with_oauth()
993            .build()
994            .await;
995        alice
996            .encryption()
997            .bootstrap_cross_signing(None)
998            .await
999            .expect("Alice should be able to set up cross signing");
1000
1001        // Prepare the login granting future using the QR code.
1002        let oauth = alice.oauth();
1003        let grant = oauth
1004            .grant_login_with_qr_code()
1005            .device_creation_timeout(Duration::from_secs(2))
1006            .scan(&qr_code_data);
1007        let secrets_bundle = export_secrets_bundle(&alice)
1008            .await
1009            .expect("Alice should be able to export the secrets bundle");
1010        let (checkcode_tx, checkcode_rx) = oneshot::channel();
1011
1012        // Spawn the updates task.
1013        let mut updates = grant.subscribe_to_progress();
1014        let mut state = grant.state.get();
1015        let verification_uri_complete =
1016            device_authorization_grant.clone().verification_uri_complete.unwrap().into_secret();
1017        assert_matches!(state.clone(), GrantLoginProgress::Starting);
1018        let updates_task = spawn(async move {
1019            let mut checkcode_tx = Some(checkcode_tx);
1020
1021            while let Some(update) = updates.next().await {
1022                match &update {
1023                    GrantLoginProgress::Starting => {
1024                        assert_matches!(state, GrantLoginProgress::Starting);
1025                    }
1026                    GrantLoginProgress::EstablishingSecureChannel(QrProgress { check_code }) => {
1027                        assert_matches!(state, GrantLoginProgress::Starting);
1028                        checkcode_tx
1029                            .take()
1030                            .expect("The checkcode should only be forwarded once")
1031                            .send(check_code.to_digit())
1032                            .expect("Alice should be able to forward the checkcode");
1033                    }
1034                    GrantLoginProgress::WaitingForAuth { verification_uri } => {
1035                        assert_matches!(
1036                            state,
1037                            GrantLoginProgress::EstablishingSecureChannel(QrProgress { .. })
1038                        );
1039                        assert_eq!(verification_uri.as_str(), verification_uri_complete);
1040                    }
1041                    GrantLoginProgress::SyncingSecrets => {
1042                        assert_matches!(state, GrantLoginProgress::WaitingForAuth { .. });
1043                    }
1044                    GrantLoginProgress::Done => {
1045                        assert_matches!(state, GrantLoginProgress::SyncingSecrets);
1046                        break;
1047                    }
1048                }
1049                state = update;
1050            }
1051        });
1052
1053        // Let Bob request the login and run through the process.
1054        let bob_task = spawn(async move {
1055            request_login_with_generated_qr_code(
1056                BobBehaviour::HappyPath,
1057                channel,
1058                checkcode_rx,
1059                login_server,
1060                rendezvous_server,
1061                alice.homeserver(),
1062                Some(device_authorization_grant),
1063                Some(secrets_bundle),
1064            )
1065            .await;
1066        });
1067
1068        // Wait for all tasks to finish.
1069        grant.await.expect("Alice should be able to grant the login");
1070        updates_task.await.expect("Alice should run through all progress states");
1071        bob_task.await.expect("Bob's task should finish");
1072    }
1073
1074    #[async_test]
1075    async fn test_grant_login_with_generated_qr_code_unexpected_message_instead_of_login_protocol()
1076    {
1077        let server = MatrixMockServer::new().await;
1078        let rendezvous_server =
1079            MockedRendezvousServer::new(server.server(), "abcdEFG12345", Duration::MAX).await;
1080        debug!("Set up rendezvous server mock at {}", rendezvous_server.rendezvous_url);
1081
1082        server.mock_upload_keys().ok().expect(1).named("upload_keys").mount().await;
1083        server
1084            .mock_upload_cross_signing_keys()
1085            .ok()
1086            .expect(1)
1087            .named("upload_xsigning_keys")
1088            .mount()
1089            .await;
1090        server
1091            .mock_upload_cross_signing_signatures()
1092            .ok()
1093            .expect(1)
1094            .named("upload_xsigning_signatures")
1095            .mount()
1096            .await;
1097
1098        // Create the existing client (Alice).
1099        let user_id = owned_user_id!("@alice:example.org");
1100        let device_id = owned_device_id!("ALICE_DEVICE");
1101        let alice = server
1102            .client_builder_for_crypto_end_to_end(&user_id, &device_id)
1103            .logged_in_with_oauth()
1104            .build()
1105            .await;
1106        alice
1107            .encryption()
1108            .bootstrap_cross_signing(None)
1109            .await
1110            .expect("Alice should be able to set up cross signing");
1111
1112        // Prepare the login granting future.
1113        let oauth = alice.oauth();
1114        let grant = oauth
1115            .grant_login_with_qr_code()
1116            .device_creation_timeout(Duration::from_secs(2))
1117            .generate();
1118        let (qr_code_tx, qr_code_rx) = oneshot::channel();
1119        let (checkcode_tx, checkcode_rx) = oneshot::channel();
1120
1121        // Spawn the updates task.
1122        let mut updates = grant.subscribe_to_progress();
1123        let mut state = grant.state.get();
1124        assert_matches!(state.clone(), GrantLoginProgress::Starting);
1125        let updates_task = spawn(async move {
1126            let mut qr_code_tx = Some(qr_code_tx);
1127            let mut checkcode_rx = Some(checkcode_rx);
1128
1129            while let Some(update) = updates.next().await {
1130                match &update {
1131                    GrantLoginProgress::Starting => {
1132                        assert_matches!(state, GrantLoginProgress::Starting);
1133                    }
1134                    GrantLoginProgress::EstablishingSecureChannel(
1135                        GeneratedQrProgress::QrReady(qr_code_data),
1136                    ) => {
1137                        assert_matches!(state, GrantLoginProgress::Starting);
1138                        qr_code_tx
1139                            .take()
1140                            .expect("The QR code should only be forwarded once")
1141                            .send(qr_code_data.clone())
1142                            .expect("Alice should be able to forward the QR code");
1143                    }
1144                    GrantLoginProgress::EstablishingSecureChannel(
1145                        GeneratedQrProgress::QrScanned(checkcode_sender),
1146                    ) => {
1147                        assert_matches!(
1148                            state,
1149                            GrantLoginProgress::EstablishingSecureChannel(
1150                                GeneratedQrProgress::QrReady(_)
1151                            )
1152                        );
1153                        let checkcode = checkcode_rx
1154                            .take()
1155                            .expect("The checkcode should only be forwarded once")
1156                            .await
1157                            .expect("Alice should receive the checkcode");
1158                        checkcode_sender
1159                            .send(checkcode)
1160                            .await
1161                            .expect("Alice should be able to forward the checkcode");
1162                        break;
1163                    }
1164                    _ => {
1165                        panic!("Alice should abort the process");
1166                    }
1167                }
1168                state = update;
1169            }
1170        });
1171
1172        // Let Bob request the login and run through the process.
1173        let bob_task = spawn(async move {
1174            request_login_with_scanned_qr_code(
1175                BobBehaviour::UnexpectedMessageInsteadOfLoginProtocol,
1176                qr_code_rx,
1177                checkcode_tx,
1178                server,
1179                rendezvous_server,
1180                None,
1181                None,
1182            )
1183            .await;
1184        });
1185
1186        // Wait for all tasks to finish / fail.
1187        grant.await.expect_err("Alice should abort the login");
1188        updates_task.await.expect("Alice should run through all progress states");
1189        bob_task.await.expect("Bob's task should finish");
1190    }
1191
1192    #[async_test]
1193    async fn test_grant_login_with_scanned_qr_code_unexpected_message_instead_of_login_protocol() {
1194        let server = MatrixMockServer::new().await;
1195        let rendezvous_server =
1196            MockedRendezvousServer::new(server.server(), "abcdEFG12345", Duration::MAX).await;
1197        debug!("Set up rendezvous server mock at {}", rendezvous_server.rendezvous_url);
1198
1199        server.mock_upload_keys().ok().expect(1).named("upload_keys").mount().await;
1200        server
1201            .mock_upload_cross_signing_keys()
1202            .ok()
1203            .expect(1)
1204            .named("upload_xsigning_keys")
1205            .mount()
1206            .await;
1207        server
1208            .mock_upload_cross_signing_signatures()
1209            .ok()
1210            .expect(1)
1211            .named("upload_xsigning_signatures")
1212            .mount()
1213            .await;
1214
1215        // Create a secure channel on the new client (Bob) and extract the QR code.
1216        let client = HttpClient::new(reqwest::Client::new(), Default::default());
1217        let channel = SecureChannel::login(client, &rendezvous_server.homeserver_url)
1218            .await
1219            .expect("Bob should be able to create a secure channel.");
1220        let qr_code_data = channel.qr_code_data().clone();
1221
1222        // Create the existing client (Alice).
1223        let user_id = owned_user_id!("@alice:example.org");
1224        let device_id = owned_device_id!("ALICE_DEVICE");
1225        let alice = server
1226            .client_builder_for_crypto_end_to_end(&user_id, &device_id)
1227            .logged_in_with_oauth()
1228            .build()
1229            .await;
1230        alice
1231            .encryption()
1232            .bootstrap_cross_signing(None)
1233            .await
1234            .expect("Alice should be able to set up cross signing");
1235
1236        // Prepare the login granting future using the QR code.
1237        let oauth = alice.oauth();
1238        let grant = oauth
1239            .grant_login_with_qr_code()
1240            .device_creation_timeout(Duration::from_secs(2))
1241            .scan(&qr_code_data);
1242        let (checkcode_tx, checkcode_rx) = oneshot::channel();
1243
1244        // Spawn the updates task.
1245        let mut updates = grant.subscribe_to_progress();
1246        let mut state = grant.state.get();
1247        assert_matches!(state.clone(), GrantLoginProgress::Starting);
1248        let updates_task = spawn(async move {
1249            let mut checkcode_tx = Some(checkcode_tx);
1250
1251            while let Some(update) = updates.next().await {
1252                match &update {
1253                    GrantLoginProgress::Starting => {
1254                        assert_matches!(state, GrantLoginProgress::Starting);
1255                    }
1256                    GrantLoginProgress::EstablishingSecureChannel(QrProgress { check_code }) => {
1257                        assert_matches!(state, GrantLoginProgress::Starting);
1258                        checkcode_tx
1259                            .take()
1260                            .expect("The checkcode should only be forwarded once")
1261                            .send(check_code.to_digit())
1262                            .expect("Alice should be able to forward the checkcode");
1263                        break;
1264                    }
1265                    _ => {
1266                        panic!("Alice should abort the process");
1267                    }
1268                }
1269                state = update;
1270            }
1271        });
1272
1273        // Let Bob request the login and run through the process.
1274        let bob_task = spawn(async move {
1275            request_login_with_generated_qr_code(
1276                BobBehaviour::UnexpectedMessageInsteadOfLoginProtocol,
1277                channel,
1278                checkcode_rx,
1279                server,
1280                rendezvous_server,
1281                alice.homeserver(),
1282                None,
1283                None,
1284            )
1285            .await;
1286        });
1287
1288        grant.await.expect_err("Alice should abort the login");
1289
1290        // Wait for all tasks to finish / fail.
1291        updates_task.await.expect("Alice should run through all progress states");
1292        bob_task.await.expect("Bob's task should finish");
1293    }
1294
1295    #[async_test]
1296    async fn test_grant_login_with_generated_qr_code_device_already_exists() {
1297        let server = MatrixMockServer::new().await;
1298        let rendezvous_server =
1299            MockedRendezvousServer::new(server.server(), "abcdEFG12345", Duration::MAX).await;
1300        debug!("Set up rendezvous server mock at {}", rendezvous_server.rendezvous_url);
1301
1302        let device_authorization_grant = AuthorizationGrant {
1303            verification_uri_complete: Some(VerificationUriComplete::new(
1304                "https://id.matrix.org/device/abcde".to_owned(),
1305            )),
1306            verification_uri: EndUserVerificationUrl::new(
1307                "https://id.matrix.org/device/abcde?code=ABCDE".to_owned(),
1308            )
1309            .unwrap(),
1310        };
1311
1312        server.mock_upload_keys().ok().expect(1).named("upload_keys").mount().await;
1313        server
1314            .mock_upload_cross_signing_keys()
1315            .ok()
1316            .expect(1)
1317            .named("upload_xsigning_keys")
1318            .mount()
1319            .await;
1320        server
1321            .mock_upload_cross_signing_signatures()
1322            .ok()
1323            .expect(1)
1324            .named("upload_xsigning_signatures")
1325            .mount()
1326            .await;
1327
1328        // Create the existing client (Alice).
1329        let user_id = owned_user_id!("@alice:example.org");
1330        let device_id = owned_device_id!("ALICE_DEVICE");
1331        let alice = server
1332            .client_builder_for_crypto_end_to_end(&user_id, &device_id)
1333            .logged_in_with_oauth()
1334            .build()
1335            .await;
1336        alice
1337            .encryption()
1338            .bootstrap_cross_signing(None)
1339            .await
1340            .expect("Alice should be able to set up cross signing");
1341
1342        // Prepare the login granting future.
1343        let oauth = alice.oauth();
1344        let grant = oauth
1345            .grant_login_with_qr_code()
1346            .device_creation_timeout(Duration::from_secs(2))
1347            .generate();
1348        let (qr_code_tx, qr_code_rx) = oneshot::channel();
1349        let (checkcode_tx, checkcode_rx) = oneshot::channel();
1350
1351        // Spawn the updates task.
1352        let mut updates = grant.subscribe_to_progress();
1353        let mut state = grant.state.get();
1354        assert_matches!(state.clone(), GrantLoginProgress::Starting);
1355        let updates_task = spawn(async move {
1356            let mut qr_code_tx = Some(qr_code_tx);
1357            let mut checkcode_rx = Some(checkcode_rx);
1358
1359            while let Some(update) = updates.next().await {
1360                match &update {
1361                    GrantLoginProgress::Starting => {
1362                        assert_matches!(state, GrantLoginProgress::Starting);
1363                    }
1364                    GrantLoginProgress::EstablishingSecureChannel(
1365                        GeneratedQrProgress::QrReady(qr_code_data),
1366                    ) => {
1367                        assert_matches!(state, GrantLoginProgress::Starting);
1368                        qr_code_tx
1369                            .take()
1370                            .expect("The QR code should only be forwarded once")
1371                            .send(qr_code_data.clone())
1372                            .expect("Alice should be able to forward the QR code");
1373                    }
1374                    GrantLoginProgress::EstablishingSecureChannel(
1375                        GeneratedQrProgress::QrScanned(checkcode_sender),
1376                    ) => {
1377                        assert_matches!(
1378                            state,
1379                            GrantLoginProgress::EstablishingSecureChannel(
1380                                GeneratedQrProgress::QrReady(_)
1381                            )
1382                        );
1383                        let checkcode = checkcode_rx
1384                            .take()
1385                            .expect("The checkcode should only be forwarded once")
1386                            .await
1387                            .expect("Alice should receive the checkcode");
1388                        checkcode_sender
1389                            .send(checkcode)
1390                            .await
1391                            .expect("Alice should be able to forward the checkcode");
1392                    }
1393                    _ => {
1394                        panic!("Alice should abort the process");
1395                    }
1396                }
1397                state = update;
1398            }
1399        });
1400
1401        // Let Bob request the login and run through the process.
1402        let bob_task = spawn(async move {
1403            request_login_with_scanned_qr_code(
1404                BobBehaviour::DeviceAlreadyExists,
1405                qr_code_rx,
1406                checkcode_tx,
1407                server,
1408                rendezvous_server,
1409                Some(device_authorization_grant),
1410                None,
1411            )
1412            .await;
1413        });
1414
1415        // Wait for all tasks to finish.
1416        grant.await.expect_err("Alice should abort the login");
1417        updates_task.await.expect("Alice should run through all progress states");
1418        bob_task.await.expect("Bob's task should finish");
1419    }
1420
1421    #[async_test]
1422    async fn test_grant_login_with_scanned_qr_code_device_already_exists() {
1423        let server = MatrixMockServer::new().await;
1424        let rendezvous_server =
1425            MockedRendezvousServer::new(server.server(), "abcdEFG12345", Duration::MAX).await;
1426        debug!("Set up rendezvous server mock at {}", rendezvous_server.rendezvous_url);
1427
1428        let device_authorization_grant = AuthorizationGrant {
1429            verification_uri_complete: Some(VerificationUriComplete::new(
1430                "https://id.matrix.org/device/abcde".to_owned(),
1431            )),
1432            verification_uri: EndUserVerificationUrl::new(
1433                "https://id.matrix.org/device/abcde?code=ABCDE".to_owned(),
1434            )
1435            .unwrap(),
1436        };
1437
1438        server.mock_upload_keys().ok().expect(1).named("upload_keys").mount().await;
1439        server
1440            .mock_upload_cross_signing_keys()
1441            .ok()
1442            .expect(1)
1443            .named("upload_xsigning_keys")
1444            .mount()
1445            .await;
1446        server
1447            .mock_upload_cross_signing_signatures()
1448            .ok()
1449            .expect(1)
1450            .named("upload_xsigning_signatures")
1451            .mount()
1452            .await;
1453
1454        // Create a secure channel on the new client (Bob) and extract the QR code.
1455        let client = HttpClient::new(reqwest::Client::new(), Default::default());
1456        let channel = SecureChannel::login(client, &rendezvous_server.homeserver_url)
1457            .await
1458            .expect("Bob should be able to create a secure channel.");
1459        let qr_code_data = channel.qr_code_data().clone();
1460
1461        // Create the existing client (Alice).
1462        let user_id = owned_user_id!("@alice:example.org");
1463        let device_id = owned_device_id!("ALICE_DEVICE");
1464        let alice = server
1465            .client_builder_for_crypto_end_to_end(&user_id, &device_id)
1466            .logged_in_with_oauth()
1467            .build()
1468            .await;
1469        alice
1470            .encryption()
1471            .bootstrap_cross_signing(None)
1472            .await
1473            .expect("Alice should be able to set up cross signing");
1474
1475        // Prepare the login granting future using the QR code.
1476        let oauth = alice.oauth();
1477        let grant = oauth
1478            .grant_login_with_qr_code()
1479            .device_creation_timeout(Duration::from_secs(2))
1480            .scan(&qr_code_data);
1481        let (checkcode_tx, checkcode_rx) = oneshot::channel();
1482
1483        // Spawn the updates task.
1484        let mut updates = grant.subscribe_to_progress();
1485        let mut state = grant.state.get();
1486        assert_matches!(state.clone(), GrantLoginProgress::Starting);
1487        let updates_task = spawn(async move {
1488            let mut checkcode_tx = Some(checkcode_tx);
1489
1490            while let Some(update) = updates.next().await {
1491                match &update {
1492                    GrantLoginProgress::Starting => {
1493                        assert_matches!(state, GrantLoginProgress::Starting);
1494                    }
1495                    GrantLoginProgress::EstablishingSecureChannel(QrProgress { check_code }) => {
1496                        assert_matches!(state, GrantLoginProgress::Starting);
1497                        checkcode_tx
1498                            .take()
1499                            .expect("The checkcode should only be forwarded once")
1500                            .send(check_code.to_digit())
1501                            .expect("Alice should be able to forward the checkcode");
1502                    }
1503                    _ => {
1504                        panic!("Alice should abort the process");
1505                    }
1506                }
1507                state = update;
1508            }
1509        });
1510
1511        // Let Bob request the login and run through the process.
1512        let bob_task = spawn(async move {
1513            request_login_with_generated_qr_code(
1514                BobBehaviour::DeviceAlreadyExists,
1515                channel,
1516                checkcode_rx,
1517                server,
1518                rendezvous_server,
1519                alice.homeserver(),
1520                Some(device_authorization_grant),
1521                None,
1522            )
1523            .await;
1524        });
1525
1526        // Wait for all tasks to finish.
1527        grant.await.expect_err("Alice should abort the login");
1528        updates_task.await.expect("Alice should run through all progress states");
1529        bob_task.await.expect("Bob's task should finish");
1530    }
1531
1532    #[async_test]
1533    async fn test_grant_login_with_generated_qr_code_device_not_created() {
1534        let server = MatrixMockServer::new().await;
1535        let rendezvous_server =
1536            MockedRendezvousServer::new(server.server(), "abcdEFG12345", Duration::MAX).await;
1537        debug!("Set up rendezvous server mock at {}", rendezvous_server.rendezvous_url);
1538
1539        let device_authorization_grant = AuthorizationGrant {
1540            verification_uri_complete: Some(VerificationUriComplete::new(
1541                "https://id.matrix.org/device/abcde".to_owned(),
1542            )),
1543            verification_uri: EndUserVerificationUrl::new(
1544                "https://id.matrix.org/device/abcde?code=ABCDE".to_owned(),
1545            )
1546            .unwrap(),
1547        };
1548
1549        server.mock_upload_keys().ok().expect(1).named("upload_keys").mount().await;
1550        server
1551            .mock_upload_cross_signing_keys()
1552            .ok()
1553            .expect(1)
1554            .named("upload_xsigning_keys")
1555            .mount()
1556            .await;
1557        server
1558            .mock_upload_cross_signing_signatures()
1559            .ok()
1560            .expect(1)
1561            .named("upload_xsigning_signatures")
1562            .mount()
1563            .await;
1564
1565        // Create the existing client (Alice).
1566        let user_id = owned_user_id!("@alice:example.org");
1567        let device_id = owned_device_id!("ALICE_DEVICE");
1568        let alice = server
1569            .client_builder_for_crypto_end_to_end(&user_id, &device_id)
1570            .logged_in_with_oauth()
1571            .build()
1572            .await;
1573        alice
1574            .encryption()
1575            .bootstrap_cross_signing(None)
1576            .await
1577            .expect("Alice should be able to set up cross signing");
1578
1579        // Prepare the login granting future.
1580        let oauth = alice.oauth();
1581        let grant = oauth
1582            .grant_login_with_qr_code()
1583            .device_creation_timeout(Duration::from_secs(2))
1584            .generate();
1585        let (qr_code_tx, qr_code_rx) = oneshot::channel();
1586        let (checkcode_tx, checkcode_rx) = oneshot::channel();
1587
1588        // Spawn the updates task.
1589        let mut updates = grant.subscribe_to_progress();
1590        let mut state = grant.state.get();
1591        let verification_uri_complete =
1592            device_authorization_grant.clone().verification_uri_complete.unwrap().into_secret();
1593        assert_matches!(state.clone(), GrantLoginProgress::Starting);
1594        let updates_task = spawn(async move {
1595            let mut qr_code_tx = Some(qr_code_tx);
1596            let mut checkcode_rx = Some(checkcode_rx);
1597
1598            while let Some(update) = updates.next().await {
1599                match &update {
1600                    GrantLoginProgress::Starting => {
1601                        assert_matches!(state, GrantLoginProgress::Starting);
1602                    }
1603                    GrantLoginProgress::EstablishingSecureChannel(
1604                        GeneratedQrProgress::QrReady(qr_code_data),
1605                    ) => {
1606                        assert_matches!(state, GrantLoginProgress::Starting);
1607                        qr_code_tx
1608                            .take()
1609                            .expect("The QR code should only be forwarded once")
1610                            .send(qr_code_data.clone())
1611                            .expect("Alice should be able to forward the QR code");
1612                    }
1613                    GrantLoginProgress::EstablishingSecureChannel(
1614                        GeneratedQrProgress::QrScanned(checkcode_sender),
1615                    ) => {
1616                        assert_matches!(
1617                            state,
1618                            GrantLoginProgress::EstablishingSecureChannel(
1619                                GeneratedQrProgress::QrReady(_)
1620                            )
1621                        );
1622                        let checkcode = checkcode_rx
1623                            .take()
1624                            .expect("The checkcode should only be forwarded once")
1625                            .await
1626                            .expect("Alice should receive the checkcode");
1627                        checkcode_sender
1628                            .send(checkcode)
1629                            .await
1630                            .expect("Alice should be able to forward the checkcode");
1631                    }
1632                    GrantLoginProgress::WaitingForAuth { verification_uri } => {
1633                        assert_matches!(
1634                            state,
1635                            GrantLoginProgress::EstablishingSecureChannel(
1636                                GeneratedQrProgress::QrScanned(_)
1637                            )
1638                        );
1639                        assert_eq!(verification_uri.as_str(), verification_uri_complete);
1640                    }
1641                    _ => {
1642                        panic!("Alice should abort the process");
1643                    }
1644                }
1645                state = update;
1646            }
1647        });
1648
1649        // Let Bob request the login and run through the process.
1650        let bob_task = spawn(async move {
1651            request_login_with_scanned_qr_code(
1652                BobBehaviour::DeviceNotCreated,
1653                qr_code_rx,
1654                checkcode_tx,
1655                server,
1656                rendezvous_server,
1657                Some(device_authorization_grant),
1658                None,
1659            )
1660            .await;
1661        });
1662
1663        grant.await.expect_err("Alice should abort the login");
1664        updates_task.await.expect("Alice should run through all progress states");
1665        bob_task.await.expect("Bob's task should finish");
1666    }
1667
1668    #[async_test]
1669    async fn test_grant_login_with_scanned_qr_code_device_not_created() {
1670        let server = MatrixMockServer::new().await;
1671        let rendezvous_server =
1672            MockedRendezvousServer::new(server.server(), "abcdEFG12345", Duration::MAX).await;
1673        debug!("Set up rendezvous server mock at {}", rendezvous_server.rendezvous_url);
1674
1675        let device_authorization_grant = AuthorizationGrant {
1676            verification_uri_complete: Some(VerificationUriComplete::new(
1677                "https://id.matrix.org/device/abcde".to_owned(),
1678            )),
1679            verification_uri: EndUserVerificationUrl::new(
1680                "https://id.matrix.org/device/abcde?code=ABCDE".to_owned(),
1681            )
1682            .unwrap(),
1683        };
1684
1685        server.mock_upload_keys().ok().expect(1).named("upload_keys").mount().await;
1686        server
1687            .mock_upload_cross_signing_keys()
1688            .ok()
1689            .expect(1)
1690            .named("upload_xsigning_keys")
1691            .mount()
1692            .await;
1693        server
1694            .mock_upload_cross_signing_signatures()
1695            .ok()
1696            .expect(1)
1697            .named("upload_xsigning_signatures")
1698            .mount()
1699            .await;
1700
1701        // Create a secure channel on the new client (Bob) and extract the QR code.
1702        let client = HttpClient::new(reqwest::Client::new(), Default::default());
1703        let channel = SecureChannel::login(client, &rendezvous_server.homeserver_url)
1704            .await
1705            .expect("Bob should be able to create a secure channel.");
1706        let qr_code_data = channel.qr_code_data().clone();
1707
1708        // Create the existing client (Alice).
1709        let user_id = owned_user_id!("@alice:example.org");
1710        let device_id = owned_device_id!("ALICE_DEVICE");
1711        let alice = server
1712            .client_builder_for_crypto_end_to_end(&user_id, &device_id)
1713            .logged_in_with_oauth()
1714            .build()
1715            .await;
1716        alice
1717            .encryption()
1718            .bootstrap_cross_signing(None)
1719            .await
1720            .expect("Alice should be able to set up cross signing");
1721
1722        // Prepare the login granting future using the QR code.
1723        let oauth = alice.oauth();
1724        let grant = oauth
1725            .grant_login_with_qr_code()
1726            .device_creation_timeout(Duration::from_secs(2))
1727            .scan(&qr_code_data);
1728        let (checkcode_tx, checkcode_rx) = oneshot::channel();
1729
1730        // Spawn the updates task.
1731        let mut updates = grant.subscribe_to_progress();
1732        let mut state = grant.state.get();
1733        let verification_uri_complete =
1734            device_authorization_grant.clone().verification_uri_complete.unwrap().into_secret();
1735        assert_matches!(state.clone(), GrantLoginProgress::Starting);
1736        let updates_task = spawn(async move {
1737            let mut checkcode_tx = Some(checkcode_tx);
1738
1739            while let Some(update) = updates.next().await {
1740                match &update {
1741                    GrantLoginProgress::Starting => {
1742                        assert_matches!(state, GrantLoginProgress::Starting);
1743                    }
1744                    GrantLoginProgress::EstablishingSecureChannel(QrProgress { check_code }) => {
1745                        assert_matches!(state, GrantLoginProgress::Starting);
1746                        checkcode_tx
1747                            .take()
1748                            .expect("The checkcode should only be forwarded once")
1749                            .send(check_code.to_digit())
1750                            .expect("Alice should be able to forward the checkcode");
1751                    }
1752                    GrantLoginProgress::WaitingForAuth { verification_uri } => {
1753                        assert_matches!(
1754                            state,
1755                            GrantLoginProgress::EstablishingSecureChannel(QrProgress { .. })
1756                        );
1757                        assert_eq!(verification_uri.as_str(), verification_uri_complete);
1758                    }
1759                    _ => {
1760                        panic!("Alice should abort the process");
1761                    }
1762                }
1763                state = update;
1764            }
1765        });
1766
1767        // Let Bob request the login and run through the process.
1768        let bob_task = spawn(async move {
1769            request_login_with_generated_qr_code(
1770                BobBehaviour::DeviceNotCreated,
1771                channel,
1772                checkcode_rx,
1773                server,
1774                rendezvous_server,
1775                alice.homeserver(),
1776                Some(device_authorization_grant),
1777                None,
1778            )
1779            .await;
1780        });
1781
1782        grant.await.expect_err("Alice should abort the login");
1783        updates_task.await.expect("Alice should run through all progress states");
1784        bob_task.await.expect("Bob's task should finish");
1785    }
1786
1787    #[async_test]
1788    async fn test_grant_login_with_generated_qr_code_session_expired() {
1789        let server = MatrixMockServer::new().await;
1790        let rendezvous_server =
1791            MockedRendezvousServer::new(server.server(), "abcdEFG12345", Duration::from_secs(2))
1792                .await;
1793        debug!("Set up rendezvous server mock at {}", rendezvous_server.rendezvous_url);
1794
1795        server.mock_upload_keys().ok().expect(1).named("upload_keys").mount().await;
1796        server
1797            .mock_upload_cross_signing_keys()
1798            .ok()
1799            .expect(1)
1800            .named("upload_xsigning_keys")
1801            .mount()
1802            .await;
1803        server
1804            .mock_upload_cross_signing_signatures()
1805            .ok()
1806            .expect(1)
1807            .named("upload_xsigning_signatures")
1808            .mount()
1809            .await;
1810
1811        // Create the existing client (Alice).
1812        let user_id = owned_user_id!("@alice:example.org");
1813        let device_id = owned_device_id!("ALICE_DEVICE");
1814        let alice = server
1815            .client_builder_for_crypto_end_to_end(&user_id, &device_id)
1816            .logged_in_with_oauth()
1817            .build()
1818            .await;
1819        alice
1820            .encryption()
1821            .bootstrap_cross_signing(None)
1822            .await
1823            .expect("Alice should be able to set up cross signing");
1824
1825        // Prepare the login granting future.
1826        let oauth = alice.oauth();
1827        let grant = oauth
1828            .grant_login_with_qr_code()
1829            .device_creation_timeout(Duration::from_secs(2))
1830            .generate();
1831
1832        // Spawn the updates task.
1833        let mut updates = grant.subscribe_to_progress();
1834        let mut state = grant.state.get();
1835        assert_matches!(state.clone(), GrantLoginProgress::Starting);
1836        let updates_task = spawn(async move {
1837            while let Some(update) = updates.next().await {
1838                match &update {
1839                    GrantLoginProgress::Starting => {
1840                        assert_matches!(state, GrantLoginProgress::Starting);
1841                    }
1842                    GrantLoginProgress::EstablishingSecureChannel(
1843                        GeneratedQrProgress::QrReady(_),
1844                    ) => {
1845                        assert_matches!(state, GrantLoginProgress::Starting);
1846                    }
1847                    _ => {
1848                        panic!("Alice should abort the process");
1849                    }
1850                }
1851                state = update;
1852            }
1853        });
1854
1855        // Bob does not scan the QR code and the channel is never connected.
1856
1857        // Wait for the rendezvous session to time out.
1858        assert_matches!(grant.await, Err(QRCodeGrantLoginError::NotFound));
1859        updates_task.await.expect("Alice should run through all progress states");
1860    }
1861
1862    #[async_test]
1863    async fn test_grant_login_with_scanned_qr_code_session_expired() {
1864        let server = MatrixMockServer::new().await;
1865        let rendezvous_server =
1866            MockedRendezvousServer::new(server.server(), "abcdEFG12345", Duration::from_secs(2))
1867                .await;
1868        debug!("Set up rendezvous server mock at {}", rendezvous_server.rendezvous_url);
1869
1870        server.mock_upload_keys().ok().expect(1).named("upload_keys").mount().await;
1871        server
1872            .mock_upload_cross_signing_keys()
1873            .ok()
1874            .expect(1)
1875            .named("upload_xsigning_keys")
1876            .mount()
1877            .await;
1878        server
1879            .mock_upload_cross_signing_signatures()
1880            .ok()
1881            .expect(1)
1882            .named("upload_xsigning_signatures")
1883            .mount()
1884            .await;
1885
1886        // Create a secure channel on the new client (Bob) and extract the QR code.
1887        let client = HttpClient::new(reqwest::Client::new(), Default::default());
1888        let channel = SecureChannel::login(client, &rendezvous_server.homeserver_url)
1889            .await
1890            .expect("Bob should be able to create a secure channel.");
1891        let qr_code_data = channel.qr_code_data().clone();
1892
1893        // Create the existing client (Alice).
1894        let user_id = owned_user_id!("@alice:example.org");
1895        let device_id = owned_device_id!("ALICE_DEVICE");
1896        let alice = server
1897            .client_builder_for_crypto_end_to_end(&user_id, &device_id)
1898            .logged_in_with_oauth()
1899            .build()
1900            .await;
1901        alice
1902            .encryption()
1903            .bootstrap_cross_signing(None)
1904            .await
1905            .expect("Alice should be able to set up cross signing");
1906
1907        // Prepare the login granting future using the QR code.
1908        let oauth = alice.oauth();
1909        let grant = oauth
1910            .grant_login_with_qr_code()
1911            .device_creation_timeout(Duration::from_secs(2))
1912            .scan(&qr_code_data);
1913
1914        // Spawn the updates task.
1915        let mut updates = grant.subscribe_to_progress();
1916        let mut state = grant.state.get();
1917        assert_matches!(state.clone(), GrantLoginProgress::Starting);
1918        let updates_task = spawn(async move {
1919            while let Some(update) = updates.next().await {
1920                match &update {
1921                    GrantLoginProgress::Starting => {
1922                        assert_matches!(state, GrantLoginProgress::Starting);
1923                    }
1924                    GrantLoginProgress::EstablishingSecureChannel(QrProgress { .. }) => {
1925                        assert_matches!(state, GrantLoginProgress::Starting);
1926                    }
1927                    _ => {
1928                        panic!("Alice should abort the process");
1929                    }
1930                }
1931                state = update;
1932            }
1933        });
1934
1935        // Bob does not connect the channel.
1936
1937        // Wait for the rendezvous session to time out.
1938        assert_matches!(grant.await, Err(QRCodeGrantLoginError::NotFound));
1939        updates_task.await.expect("Alice should run through all progress states");
1940    }
1941}