example_qr_login/
main.rs

1use std::io::Write;
2
3use anyhow::{bail, Context, Result};
4use clap::Parser;
5use futures_util::StreamExt;
6use matrix_sdk::{
7    authentication::oauth::{
8        qrcode::{LoginProgress, QrCodeData, QrCodeModeData},
9        registration::{ApplicationType, ClientMetadata, Localized, OAuthGrantType},
10    },
11    ruma::serde::Raw,
12    Client,
13};
14use url::Url;
15
16/// A command line example showcasing how to login using a QR code.
17///
18/// Another device, which will display the QR code is needed to use this
19/// example.
20#[derive(Parser, Debug)]
21struct Cli {
22    /// Set the proxy that should be used for the connection.
23    #[clap(short, long)]
24    proxy: Option<Url>,
25
26    /// Enable verbose logging output.
27    #[clap(short, long, action)]
28    verbose: bool,
29}
30
31/// Generate the OAuth 2.0 client metadata.
32fn client_metadata() -> Raw<ClientMetadata> {
33    let client_uri = Localized::new(
34        Url::parse("https://github.com/matrix-org/matrix-rust-sdk")
35            .expect("Couldn't parse client URI"),
36        None,
37    );
38
39    let metadata = ClientMetadata {
40        // The following fields should be displayed in the OAuth 2.0 authorization server's web UI
41        // as part of the process to get the user's consent. It means that these should
42        // contain real data so the user can make sure that they allow the proper
43        // application. We are cheating here because this is an example.
44        client_name: Some(Localized::new("matrix-rust-sdk-qrlogin".to_owned(), [])),
45        policy_uri: Some(client_uri.clone()),
46        tos_uri: Some(client_uri.clone()),
47        ..ClientMetadata::new(
48            // This is a native application (in contrast to a web application, that runs in a
49            // browser).
50            ApplicationType::Native,
51            // We are going to use the Device Authorization flow.
52            vec![OAuthGrantType::DeviceCode],
53            client_uri,
54        )
55    };
56
57    Raw::new(&metadata).expect("Couldn't serialize client metadata")
58}
59
60async fn print_devices(client: &Client) -> Result<()> {
61    let user_id = client.user_id().unwrap();
62    let own_device =
63        client.encryption().get_own_device().await?.expect("We should have our own device by now");
64
65    println!(
66        "Status of our own device {}",
67        if own_device.is_cross_signed_by_owner() { "✅" } else { "❌" }
68    );
69
70    println!("Devices of user {user_id}");
71
72    for device in client.encryption().get_user_devices(user_id).await?.devices() {
73        if device.device_id()
74            == client.device_id().expect("We should be logged in now and know our device id")
75        {
76            continue;
77        }
78
79        println!(
80            "   {:<10} {:<30} {:<}",
81            device.device_id(),
82            device.display_name().unwrap_or("-"),
83            if device.is_verified() { "✅" } else { "❌" }
84        );
85    }
86
87    Ok(())
88}
89
90async fn login(proxy: Option<Url>) -> Result<()> {
91    println!("Please scan the QR code and convert the data to base64 before entering it here.");
92    println!("On Linux/Wayland, this can be achieved using the following command line:");
93    println!(
94        "    $ grim -g \"$(slurp)\" - | zbarimg --oneshot -Sbinary PNG:- | base64 -w 0 | wl-copy"
95    );
96    println!("Paste the QR code data here: ");
97
98    let mut input = String::new();
99    std::io::stdin().read_line(&mut input).expect("error: unable to read user input");
100    let input = input.trim();
101
102    let data = QrCodeData::from_base64(input).context("Couldn't parse the base64 QR code data")?;
103
104    let QrCodeModeData::Reciprocate { server_name } = &data.mode_data else {
105        bail!("The QR code is invalid, we did not receive a homeserver in the QR code.");
106    };
107    let mut client = Client::builder().server_name_or_homeserver_url(server_name);
108
109    if let Some(proxy) = proxy {
110        client = client.proxy(proxy).disable_ssl_verification();
111    }
112
113    let client = client.build().await?;
114
115    let metadata = client_metadata();
116    let oauth = client.oauth();
117
118    let login_client = oauth.login_with_qr_code(&data, metadata.into());
119    let mut subscriber = login_client.subscribe_to_progress();
120
121    let task = tokio::spawn(async move {
122        while let Some(state) = subscriber.next().await {
123            match state {
124                LoginProgress::Starting => (),
125                LoginProgress::EstablishingSecureChannel { check_code } => {
126                    let code = check_code.to_digit();
127                    println!("Please enter the following code into the other device {code:02}");
128                }
129                LoginProgress::WaitingForToken { user_code } => {
130                    println!("Please use your other device to confirm the log in {user_code}")
131                }
132                LoginProgress::Done => break,
133            }
134        }
135
136        std::io::stdout().flush().expect("Unable to write to stdout");
137    });
138
139    let result = login_client.await;
140    task.abort();
141
142    result?;
143
144    let status = client.encryption().cross_signing_status().await.unwrap();
145    let user_id = client.user_id().unwrap();
146
147    println!(
148        "Successfully logged in as {user_id} using the qr code, cross-signing status: {status:?}"
149    );
150
151    print_devices(&client).await?;
152
153    Ok(())
154}
155
156#[tokio::main]
157async fn main() -> Result<()> {
158    let cli = Cli::parse();
159
160    if cli.verbose {
161        tracing_subscriber::fmt::init();
162    }
163
164    login(cli.proxy).await?;
165
166    Ok(())
167}