1use std::io::Write;
2
3use anyhow::{Context, Result, bail};
4use clap::Parser;
5use futures_util::StreamExt;
6use matrix_sdk::{
7 Client,
8 authentication::oauth::{
9 qrcode::{LoginProgress, Msc4108IntentData, QrCodeData, QrCodeIntentData, QrProgress},
10 registration::{ApplicationType, ClientMetadata, Localized, OAuthGrantType},
11 },
12 ruma::serde::Raw,
13};
14use url::Url;
15
16#[derive(Parser, Debug)]
21struct Cli {
22 #[clap(short, long)]
24 proxy: Option<Url>,
25
26 #[clap(short, long, action)]
28 verbose: bool,
29}
30
31fn 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 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 ApplicationType::Native,
51 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 QrCodeIntentData::Msc4108 { data: Msc4108IntentData::Reciprocate { server_name }, .. } =
105 &data.intent_data()
106 else {
107 bail!("The QR code is invalid, we did not receive a homeserver in the QR code.");
108 };
109 let mut client = Client::builder().server_name_or_homeserver_url(server_name);
110
111 if let Some(proxy) = proxy {
112 client = client.proxy(proxy).disable_ssl_verification();
113 }
114
115 let client = client.build().await?;
116
117 let registration_data = client_metadata().into();
118 let oauth = client.oauth();
119
120 let login_client = oauth.login_with_qr_code(Some(®istration_data)).scan(&data);
121 let mut subscriber = login_client.subscribe_to_progress();
122
123 let task = tokio::spawn(async move {
124 while let Some(state) = subscriber.next().await {
125 match state {
126 LoginProgress::Starting | LoginProgress::SyncingSecrets => (),
127 LoginProgress::EstablishingSecureChannel(QrProgress { check_code }) => {
128 let code = check_code.to_digit();
129 println!("Please enter the following code into the other device {code:02}");
130 }
131 LoginProgress::WaitingForToken { user_code } => {
132 println!("Please use your other device to confirm the log in {user_code}")
133 }
134 LoginProgress::Done => break,
135 }
136 }
137
138 std::io::stdout().flush().expect("Unable to write to stdout");
139 });
140
141 let result = login_client.await;
142 task.abort();
143
144 result?;
145
146 let status = client.encryption().cross_signing_status().await.unwrap();
147 let user_id = client.user_id().unwrap();
148
149 println!(
150 "Successfully logged in as {user_id} using the qr code, cross-signing status: {status:?}"
151 );
152
153 print_devices(&client).await?;
154
155 Ok(())
156}
157
158#[tokio::main]
159async fn main() -> Result<()> {
160 let cli = Cli::parse();
161
162 if cli.verbose {
163 tracing_subscriber::fmt::init();
164 }
165
166 login(cli.proxy).await?;
167
168 Ok(())
169}