Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/matrix-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ All notable changes to this project will be documented in this file.

### Features

- Extend `authentication::oauth::OAuth::grant_login_with_qr_code` to support granting
login by scanning a QR code on the existing device.
([#5818](https://github.com/matrix-org/matrix-rust-sdk/pull/5818))
- Add a new `RequestConfig::skip_auth()` option. This is useful to ensure that
certain request won't ever include an authorization header.
([#5822](https://github.com/matrix-org/matrix-rust-sdk/pull/5822))
Expand Down
125 changes: 120 additions & 5 deletions crates/matrix-sdk/src/authentication/oauth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@
//! [`ErrorKind::UnknownToken`]: ruma::api::client::error::ErrorKind::UnknownToken
//! [`examples/oauth_cli`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/examples/oauth_cli

#[cfg(feature = "e2e-encryption")]
use std::time::Duration;
use std::{
borrow::Cow,
collections::{BTreeSet, HashMap},
Expand Down Expand Up @@ -214,7 +216,10 @@ mod tests;
#[cfg(feature = "e2e-encryption")]
use self::cross_process::{CrossProcessRefreshLockGuard, CrossProcessRefreshManager};
#[cfg(feature = "e2e-encryption")]
use self::qrcode::{GrantLoginWithGeneratedQrCode, LoginWithGeneratedQrCode, LoginWithQrCode};
use self::qrcode::{
GrantLoginWithGeneratedQrCode, GrantLoginWithScannedQrCode, LoginWithGeneratedQrCode,
LoginWithQrCode,
};
pub use self::{
account_management_url::{AccountManagementActionFull, AccountManagementUrlBuilder},
auth_code_builder::{OAuthAuthCodeUrlBuilder, OAuthAuthorizationData},
Expand Down Expand Up @@ -383,7 +388,7 @@ impl OAuth {
/// Grant login to a new device using a QR code.
#[cfg(feature = "e2e-encryption")]
pub fn grant_login_with_qr_code<'a>(&'a self) -> GrantLoginWithQrCodeBuilder<'a> {
GrantLoginWithQrCodeBuilder { client: &self.client }
GrantLoginWithQrCodeBuilder::new(&self.client)
}

/// Restore or register the OAuth 2.0 client for the server with the given
Expand Down Expand Up @@ -1527,12 +1532,122 @@ impl<'a> LoginWithQrCodeBuilder<'a> {
pub struct GrantLoginWithQrCodeBuilder<'a> {
/// The underlying Matrix API client.
client: &'a Client,
/// The duration to wait for the homeserver to create the new device after
/// consenting the login before giving up.
device_creation_timeout: Duration,
}

#[cfg(feature = "e2e-encryption")]
impl<'a> GrantLoginWithQrCodeBuilder<'a> {
/// Grant login by generating a QR code on this device to be scanned by the
/// new device.
/// Create a new builder with the default device creation timeout.
fn new(client: &'a Client) -> Self {
Self { client, device_creation_timeout: Duration::from_secs(10) }
}

/// Set the device creation timeout.
///
/// # Arguments
///
/// * `device_creation_timeout` - The duration to wait for the homeserver to
/// create the new device after consenting the login before giving up.
pub fn device_creation_timeout(mut self, device_creation_timeout: Duration) -> Self {
self.device_creation_timeout = device_creation_timeout;
self
}

/// This method allows you to grant login to a new device by scanning a
/// QR code generated by the new device.
///
/// The new device needs to display the QR code which this device can
/// scan and call this method to grant the login.
///
/// A successful login grant using this method will automatically mark the
/// new device as verified and transfer all end-to-end encryption
/// related secrets, like the private cross-signing keys and the backup
/// key from this device device to the new device.
///
/// For the reverse flow where this device generates the QR code
/// for the new device to scan, use
/// [`GrantLoginWithQrCodeBuilder::generate`].
///
/// # Arguments
///
/// * `data` - The data scanned from a QR code.
///
/// # Example
///
/// ```no_run
/// use anyhow::bail;
/// use futures_util::StreamExt;
/// use matrix_sdk::{
/// Client, authentication::oauth::{
/// qrcode::{GrantLoginProgress, QrCodeData, QrCodeModeData, QrProgress},
/// }
/// };
/// use std::{error::Error, io::stdin};
/// # _ = async {
/// # let bytes = unimplemented!();
/// // You'll need to use a different library to scan and extract the raw bytes from the QR
/// // code.
/// let qr_code_data = QrCodeData::from_bytes(bytes)?;
///
/// // Build the client as usual.
/// let client = Client::builder()
/// .server_name_or_homeserver_url("matrix.org")
/// .handle_refresh_tokens()
/// .build()
/// .await?;
///
/// let oauth = client.oauth();
///
/// // Subscribing to the progress is necessary to capture
/// // the checkcode in order to display it to the other device and to obtain the verification URL to
/// // open it in a browser so the user can consent to the new login.
/// let mut grant = oauth.grant_login_with_qr_code().scan(&qr_code_data);
/// let mut progress = grant.subscribe_to_progress();
///
/// // Create a task which will show us the progress and allows us to receive
/// // and feed back data.
/// let task = tokio::spawn(async move {
/// while let Some(state) = progress.next().await {
/// match state {
/// GrantLoginProgress::Starting | GrantLoginProgress::SyncingSecrets => (),
/// GrantLoginProgress::EstablishingSecureChannel(QrProgress { check_code }) => {
/// println!("Please enter the checkcode on your other device: {:?}", check_code);
/// }
/// GrantLoginProgress::WaitingForAuth { verification_uri } => {
/// println!("Please open {verification_uri} to confirm the new login")
/// },
/// GrantLoginProgress::Done => break,
/// }
/// }
/// Ok::<(), Box<dyn Error + Send + Sync>>(())
/// });
///
/// // Now run the future to grant the login.
/// grant.await?;
/// task.abort();
///
/// println!("Successfully granted login");
/// # anyhow::Ok(()) };
/// ```
pub fn scan(self, data: &'a QrCodeData) -> GrantLoginWithScannedQrCode<'a> {
GrantLoginWithScannedQrCode::new(self.client, data, self.device_creation_timeout)
}

/// This method allows you to grant login to a new device by generating a QR
/// code on this device to be scanned by the new device.
///
/// This device needs to call this method to generate and display the
/// QR code which the new device can scan to initiate the grant process.
///
/// A successful login grant using this method will automatically mark the
/// new device as verified and transfer all end-to-end encryption
/// related secrets, like the private cross-signing keys and the backup
/// key from this device device to the new device.
///
/// For the reverse flow where the new device generates the QR code
/// for this device to scan, use [`GrantLoginWithQrCodeBuilder::scan`].
///
/// # Example
///
Expand Down Expand Up @@ -1594,7 +1709,7 @@ impl<'a> GrantLoginWithQrCodeBuilder<'a> {
/// # anyhow::Ok(()) };
/// ```
pub fn generate(self) -> GrantLoginWithGeneratedQrCode<'a> {
GrantLoginWithGeneratedQrCode::new(self.client)
GrantLoginWithGeneratedQrCode::new(self.client, self.device_creation_timeout)
}
}
/// A full session for the OAuth 2.0 API.
Expand Down
Loading
Loading