Skip to content

Commit 61a6987

Browse files
committed
feat(oauth): add flow for reciprocating a login using a QR code generated on the existing device
Signed-off-by: Johannes Marbach <[email protected]>
1 parent e08b851 commit 61a6987

File tree

6 files changed

+1197
-69
lines changed

6 files changed

+1197
-69
lines changed

crates/matrix-sdk/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ All notable changes to this project will be documented in this file.
1616
- [**breaking**] Add `authentication::oauth::qrcode::login::LoginProgress::SyncingSecrets` to
1717
indicate that secrets are being synced between the two devices.
1818
([#5760](https://github.com/matrix-org/matrix-rust-sdk/pull/5760))
19+
- Add `authentication::oauth::OAuth::grant_login_with_qr_code` to reciprocate a login by
20+
generating a QR code on the existing device.
21+
([#5801](https://github.com/matrix-org/matrix-rust-sdk/pull/5801))
1922

2023
### Refactor
2124

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

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ mod tests;
214214
#[cfg(feature = "e2e-encryption")]
215215
use self::cross_process::{CrossProcessRefreshLockGuard, CrossProcessRefreshManager};
216216
#[cfg(feature = "e2e-encryption")]
217-
use self::qrcode::{LoginWithGeneratedQrCode, LoginWithQrCode};
217+
use self::qrcode::{GrantLoginWithGeneratedQrCode, LoginWithGeneratedQrCode, LoginWithQrCode};
218218
pub use self::{
219219
account_management_url::{AccountManagementActionFull, AccountManagementUrlBuilder},
220220
auth_code_builder::{OAuthAuthCodeUrlBuilder, OAuthAuthorizationData},
@@ -364,7 +364,7 @@ impl OAuth {
364364
as_variant!(data, AuthData::OAuth)
365365
}
366366

367-
/// Log in using a QR code.
367+
/// Log in this device using a QR code.
368368
///
369369
/// # Arguments
370370
///
@@ -380,6 +380,12 @@ impl OAuth {
380380
LoginWithQrCodeBuilder { client: &self.client, registration_data }
381381
}
382382

383+
/// Grant login to a new device using a QR code.
384+
#[cfg(feature = "e2e-encryption")]
385+
pub fn grant_login_with_qr_code<'a>(&'a self) -> GrantLoginWithQrCodeBuilder<'a> {
386+
GrantLoginWithQrCodeBuilder { client: &self.client }
387+
}
388+
383389
/// Restore or register the OAuth 2.0 client for the server with the given
384390
/// metadata, with the given optional [`ClientRegistrationData`].
385391
///
@@ -1450,7 +1456,7 @@ impl<'a> LoginWithQrCodeBuilder<'a> {
14501456
/// ruma::serde::Raw,
14511457
/// Client,
14521458
/// };
1453-
/// use std::io::stdin;
1459+
/// use std::{error::Error, io::stdin};
14541460
/// # fn client_metadata() -> Raw<ClientMetadata> { unimplemented!() }
14551461
/// # _ = async {
14561462
/// // Build the client as usual.
@@ -1481,16 +1487,17 @@ impl<'a> LoginWithQrCodeBuilder<'a> {
14811487
/// LoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrScanned(cctx)) => {
14821488
/// println!("Please enter the code displayed on your other device");
14831489
/// let mut s = String::new();
1484-
/// stdin().read_line(&mut s).unwrap();
1485-
/// let check_code = s.trim().parse::<u8>().unwrap();
1486-
/// cctx.send(check_code).await.unwrap()
1490+
/// stdin().read_line(&mut s)?;
1491+
/// let check_code = s.trim().parse::<u8>()?;
1492+
/// cctx.send(check_code).await?
14871493
/// }
14881494
/// LoginProgress::WaitingForToken { user_code } => {
14891495
/// println!("Please use your other device to confirm the log in {user_code}")
14901496
/// },
14911497
/// LoginProgress::Done => break,
14921498
/// }
14931499
/// }
1500+
/// Ok::<(), Box<dyn Error + Send + Sync>>(())
14941501
/// });
14951502
///
14961503
/// // Now run the future to complete the login.
@@ -1505,6 +1512,82 @@ impl<'a> LoginWithQrCodeBuilder<'a> {
15051512
}
15061513
}
15071514

1515+
/// Builder for QR login grant handlers.
1516+
#[cfg(feature = "e2e-encryption")]
1517+
#[derive(Debug)]
1518+
pub struct GrantLoginWithQrCodeBuilder<'a> {
1519+
/// The underlying Matrix API client.
1520+
client: &'a Client,
1521+
}
1522+
1523+
#[cfg(feature = "e2e-encryption")]
1524+
impl<'a> GrantLoginWithQrCodeBuilder<'a> {
1525+
/// Grant login by generating a QR code on this device to be scanned by the
1526+
/// new device.
1527+
///
1528+
/// # Example
1529+
///
1530+
/// ```no_run
1531+
/// use anyhow::bail;
1532+
/// use futures_util::StreamExt;
1533+
/// use matrix_sdk::{
1534+
/// Client, authentication::oauth::{
1535+
/// qrcode::{GeneratedQrProgress, GrantLoginProgress}
1536+
/// }
1537+
/// };
1538+
/// use std::{error::Error, io::stdin};
1539+
/// # _ = async {
1540+
/// // Build the client as usual.
1541+
/// let client = Client::builder()
1542+
/// .server_name_or_homeserver_url("matrix.org")
1543+
/// .handle_refresh_tokens()
1544+
/// .build()
1545+
/// .await?;
1546+
///
1547+
/// let oauth = client.oauth();
1548+
///
1549+
/// // Subscribing to the progress is necessary since we need to capture the
1550+
/// // QR code, feed the checkcode back in and obtain the verification URL to
1551+
/// // open it in a browser so the user can consent to the new login.
1552+
/// let mut grant = oauth.grant_login_with_qr_code().generate();
1553+
/// let mut progress = grant.subscribe_to_progress();
1554+
///
1555+
/// // Create a task which will show us the progress and allows us to receive
1556+
/// // and feed back data.
1557+
/// let task = tokio::spawn(async move {
1558+
/// while let Some(state) = progress.next().await {
1559+
/// match state {
1560+
/// GrantLoginProgress::Starting | GrantLoginProgress::SyncingSecrets => (),
1561+
/// GrantLoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrReady(qr_code_data)) => {
1562+
/// println!("Please scan the QR code on your other device: {:?}", qr_code_data);
1563+
/// }
1564+
/// GrantLoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrScanned(checkcode_sender)) => {
1565+
/// println!("Please enter the code displayed on your other device");
1566+
/// let mut s = String::new();
1567+
/// stdin().read_line(&mut s)?;
1568+
/// let check_code = s.trim().parse::<u8>()?;
1569+
/// checkcode_sender.send(check_code).await?;
1570+
/// }
1571+
/// GrantLoginProgress::WaitingForAuth { verification_uri } => {
1572+
/// println!("Please open {verification_uri} to confirm the new login")
1573+
/// },
1574+
/// GrantLoginProgress::Done => break,
1575+
/// }
1576+
/// }
1577+
/// Ok::<(), Box<dyn Error + Send + Sync>>(())
1578+
/// });
1579+
///
1580+
/// // Now run the future to grant the login.
1581+
/// grant.await?;
1582+
/// task.abort();
1583+
///
1584+
/// println!("Successfully granted login");
1585+
/// # anyhow::Ok(()) };
1586+
/// ```
1587+
pub fn generate(self) -> GrantLoginWithGeneratedQrCode<'a> {
1588+
GrantLoginWithGeneratedQrCode::new(self.client)
1589+
}
1590+
}
15081591
/// A full session for the OAuth 2.0 API.
15091592
#[derive(Debug, Clone)]
15101593
pub struct OAuthSession {

0 commit comments

Comments
 (0)