1use std::io::Write;
23use 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;
1516/// 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)]
24proxy: Option<Url>,
2526/// Enable verbose logging output.
27#[clap(short, long, action)]
28verbose: bool,
29}
3031/// Generate the OAuth 2.0 client metadata.
32fn client_metadata() -> Raw<ClientMetadata> {
33let client_uri = Localized::new(
34 Url::parse("https://github.com/matrix-org/matrix-rust-sdk")
35 .expect("Couldn't parse client URI"),
36None,
37 );
3839let 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.
44client_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).
50ApplicationType::Native,
51// We are going to use the Device Authorization flow.
52vec![OAuthGrantType::DeviceCode],
53 client_uri,
54 )
55 };
5657 Raw::new(&metadata).expect("Couldn't serialize client metadata")
58}
5960async fn print_devices(client: &Client) -> Result<()> {
61let user_id = client.user_id().unwrap();
62let own_device =
63 client.encryption().get_own_device().await?.expect("We should have our own device by now");
6465println!(
66"Status of our own device {}",
67if own_device.is_cross_signed_by_owner() { "✅" } else { "❌" }
68 );
6970println!("Devices of user {user_id}");
7172for device in client.encryption().get_user_devices(user_id).await?.devices() {
73if device.device_id()
74 == client.device_id().expect("We should be logged in now and know our device id")
75 {
76continue;
77 }
7879println!(
80" {:<10} {:<30} {:<}",
81 device.device_id(),
82 device.display_name().unwrap_or("-"),
83if device.is_verified() { "✅" } else { "❌" }
84 );
85 }
8687Ok(())
88}
8990async fn login(proxy: Option<Url>) -> Result<()> {
91println!("Please scan the QR code and convert the data to base64 before entering it here.");
92println!("On Linux/Wayland, this can be achieved using the following command line:");
93println!(
94" $ grim -g \"$(slurp)\" - | zbarimg --oneshot -Sbinary PNG:- | base64 -w 0 | wl-copy"
95);
96println!("Paste the QR code data here: ");
9798let mut input = String::new();
99 std::io::stdin().read_line(&mut input).expect("error: unable to read user input");
100let input = input.trim();
101102let data = QrCodeData::from_base64(input).context("Couldn't parse the base64 QR code data")?;
103104let QrCodeModeData::Reciprocate { server_name } = &data.mode_data else {
105bail!("The QR code is invalid, we did not receive a homeserver in the QR code.");
106 };
107let mut client = Client::builder().server_name_or_homeserver_url(server_name);
108109if let Some(proxy) = proxy {
110 client = client.proxy(proxy).disable_ssl_verification();
111 }
112113let client = client.build().await?;
114115let metadata = client_metadata();
116let oauth = client.oauth();
117118let login_client = oauth.login_with_qr_code(&data, metadata.into());
119let mut subscriber = login_client.subscribe_to_progress();
120121let task = tokio::spawn(async move {
122while let Some(state) = subscriber.next().await {
123match state {
124 LoginProgress::Starting => (),
125 LoginProgress::EstablishingSecureChannel { check_code } => {
126let code = check_code.to_digit();
127println!("Please enter the following code into the other device {code:02}");
128 }
129 LoginProgress::WaitingForToken { user_code } => {
130println!("Please use your other device to confirm the log in {user_code}")
131 }
132 LoginProgress::Done => break,
133 }
134 }
135136 std::io::stdout().flush().expect("Unable to write to stdout");
137 });
138139let result = login_client.await;
140 task.abort();
141142 result?;
143144let status = client.encryption().cross_signing_status().await.unwrap();
145let user_id = client.user_id().unwrap();
146147println!(
148"Successfully logged in as {user_id} using the qr code, cross-signing status: {status:?}"
149);
150151 print_devices(&client).await?;
152153Ok(())
154}
155156#[tokio::main]
157async fn main() -> Result<()> {
158let cli = Cli::parse();
159160if cli.verbose {
161 tracing_subscriber::fmt::init();
162 }
163164 login(cli.proxy).await?;
165166Ok(())
167}