A code to scan, not seven pictures to read aloud
Verification was SAS and nothing else: compare emoji, in the same order, out loud. Element offers scanning first and it is what most people use - so verifying moho from Element meant Element offering its camera and this side having nothing to be scanned. The showing half only, and deliberately. Scanning one needs a camera or a picture to read, which is its own piece of work; on a desktop the device holding the camera is the other one, so showing is the half that matters here. Answers nothing rather than an error where a code cannot be made, because that is an ordinary state and not a failure: a QR code proves an identity the other side can already check, so it needs cross-signing set up. Without it there is nothing to encode and emoji remain the way through - which is why the dialog draws the code beside the existing flow rather than instead of it. SVG rather than a bitmap. The daemon has no idea how large this will be drawn, and a code scaled up from a fixed grid of pixels is one a camera has to work harder to read. The `qrcode` crate was already here for reading Discord's login code; this is the same crate pointed the other way. Part of #163. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Generated
+14
@@ -3531,6 +3531,7 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"js_option",
|
||||
"matrix-sdk-common",
|
||||
"matrix-sdk-qrcode",
|
||||
"pbkdf2",
|
||||
"rand 0.10.2",
|
||||
"rmp-serde",
|
||||
@@ -3550,6 +3551,19 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matrix-sdk-qrcode"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc199f424cd31ad8a63717965779f6ecc877b3ecebe31db8030ef3be8200b0cd"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"qrcode",
|
||||
"ruma",
|
||||
"thiserror 2.0.19",
|
||||
"vodozemac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matrix-sdk-sqlite"
|
||||
version = "0.18.0"
|
||||
|
||||
+5
-1
@@ -40,6 +40,8 @@ sha2 = "0.10"
|
||||
# The login captcha's proof of work; see backend/sneedchat/captcha.rs.
|
||||
argon2 = { version = "0.5", default-features = false, features = ["alloc"] }
|
||||
rand = "0.8"
|
||||
# `image` for reading Discord's login code, `svg` (a default) for drawing
|
||||
# Matrix's verification one - the same crate answers both directions.
|
||||
qrcode = { version = "0.14", features = ["image"] }
|
||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||
chrono = { version = "0.4", default-features = false, features = ["std"] }
|
||||
@@ -66,7 +68,9 @@ tokio-rustls = { version = "0.26", default-features = false, features = ["ring",
|
||||
# `experimental-send-custom-to-device` is what lets a call's media keys be
|
||||
# sent straight to the devices in the call, Olm-encrypted, which is how
|
||||
# Element distributes them - see backend/matrix/crypto.rs.
|
||||
matrix-sdk-crypto = { version = "0.18", features = ["experimental-send-custom-to-device"] }
|
||||
# `qrcode` is what lets a verification be done by scanning rather than by
|
||||
# reading emoji aloud - the method Element offers first and most people use.
|
||||
matrix-sdk-crypto = { version = "0.18", features = ["experimental-send-custom-to-device", "qrcode"] }
|
||||
# Pinned to exactly what matrix-sdk-crypto 0.18 resolves to, for the same
|
||||
# reason the ruma crates below are: this is where its own response types live
|
||||
# - the verification state a decrypted message carries among them - and a
|
||||
|
||||
@@ -260,6 +260,44 @@ pub async fn start_user_verification(state: &AppState, account_id: &str, user_id
|
||||
|
||||
/// Accepts or declines an incoming verification request (one the *other*
|
||||
/// session started - see tick's incoming-request scan below).
|
||||
/// The QR code for a verification, as an SVG to draw.
|
||||
///
|
||||
/// The method Element offers first and the one most people use: point one
|
||||
/// device at the other rather than reading seven pictures aloud in the same
|
||||
/// order. Until this existed, verifying moho from Element meant Element
|
||||
/// offering to scan and moho having nothing to be scanned.
|
||||
///
|
||||
/// This is the showing half only. Scanning one needs a camera or a picture to
|
||||
/// read, which is its own piece of work - and the showing half is the useful
|
||||
/// one on a desktop, because the device with the camera is the phone.
|
||||
///
|
||||
/// Answers None rather than an error where a code cannot be made. That is the
|
||||
/// ordinary case for a reason worth knowing: a QR code proves an identity the
|
||||
/// other side can already check, so it needs cross-signing set up. Without it
|
||||
/// there is nothing to encode, and emoji remain the way through.
|
||||
pub async fn qr_code(state: &AppState, account_id: &str, verification_id: &str) -> Result<Option<String>> {
|
||||
let Some(v) = state.runtime.get_matrix_verification(verification_id) else {
|
||||
bail!("no such verification");
|
||||
};
|
||||
if v.account_id != account_id {
|
||||
bail!("that verification belongs to another account");
|
||||
}
|
||||
let Some(qr) = v.request.generate_qr_code().await.context("making the QR code")? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let code = qr.to_qr_code().context("encoding the QR code")?;
|
||||
// SVG rather than a bitmap: the daemon has no idea how large this will be
|
||||
// drawn, and a QR code scaled up from a fixed grid of pixels is a QR code
|
||||
// a camera has to work harder to read.
|
||||
let svg = code
|
||||
.render()
|
||||
.min_dimensions(240, 240)
|
||||
.dark_color(qrcode::render::svg::Color("#000000"))
|
||||
.light_color(qrcode::render::svg::Color("#ffffff"))
|
||||
.build();
|
||||
Ok(Some(svg))
|
||||
}
|
||||
|
||||
pub async fn respond_to_request(state: &AppState, account_id: &str, verification_id: &str, accept: bool) -> Result<()> {
|
||||
let (config, session) = account_session(state, account_id).await?;
|
||||
let v = state.runtime.get_matrix_verification(verification_id).context("no such verification")?;
|
||||
|
||||
@@ -3464,6 +3464,21 @@ pub async fn dispatch(
|
||||
}
|
||||
}
|
||||
|
||||
// The QR code for a verification in progress, drawn so another device
|
||||
// can scan it. None where one cannot be made, which is the ordinary
|
||||
// case without cross-signing - a code proves an identity the other
|
||||
// side can already check, and without one there is nothing to encode.
|
||||
"matrixVerificationQrCode" => {
|
||||
let (account_id, verification_id) = match (p_str_opt(params, "accountId"), p_str_opt(params, "verificationId")) {
|
||||
(Some(a), Some(v)) => (a, v),
|
||||
_ => return (None, Some("matrixVerificationQrCode requires \"accountId\" and \"verificationId\"".to_string())),
|
||||
};
|
||||
match backend::matrix::verification::qr_code(state, account_id, verification_id).await {
|
||||
Ok(svg) => (Some(serde_json::json!({ "svg": svg })), None),
|
||||
Err(e) => (None, Some(format!("{e:#}"))),
|
||||
}
|
||||
}
|
||||
|
||||
"respondMatrixVerification" => {
|
||||
let (account_id, verification_id) = match (p_str_opt(params, "accountId"), p_str_opt(params, "verificationId")) {
|
||||
(Some(a), Some(v)) => (a, v),
|
||||
|
||||
Reference in New Issue
Block a user