feat: carry all published relay addresses in securejoin links (#8591)

Carry all published "secondary" addresses in `r` param of the securejoin URL.

Closes: #8590
Signed-off-by: Jagoda Ślązak <jslazak@jslazak.com>
This commit is contained in:
j-g00da
2026-08-18 21:19:54 +02:00
committed by GitHub
parent 33b258302b
commit 1283b986df
4 changed files with 90 additions and 13 deletions

View File

@@ -463,6 +463,8 @@ pub fn format_backup(qr: &Qr) -> Result<String> {
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH`
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&b=BROADCAST_NAME&x=BROADCAST_ID&j=INVITENUMBER&s=AUTH`
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR`
///
/// with optional `&r=ADDRS` param.
async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
let payload = qr
.get(OPENPGP4FPR_SCHEME.len()..)
@@ -492,10 +494,17 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
})
.collect();
let addr = if let Some(addr) = param.get("a") {
Some(normalize_address(addr)?)
} else {
None
let addrs = {
let mut addrs = Vec::new();
if let Some(primary_addr) = param.get("a") {
addrs.push(normalize_address(primary_addr)?);
};
if let Some(secondary_addrs_raw) = param.get("r") {
for secondary_address in secondary_addrs_raw.split(',') {
addrs.push(normalize_address(secondary_address)?)
}
}
addrs
};
let name = decode_name(&param, "n")?.unwrap_or_default();
@@ -528,7 +537,9 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
invitenumber = Some("".to_string());
}
if let (Some(addr), Some(invitenumber), Some(authcode)) = (&addr, invitenumber, authcode) {
if let (Some(addr), Some(invitenumber), Some(authcode)) =
(addrs.first(), invitenumber, authcode)
{
let addr = ContactAddress::new(addr)?;
let (contact_id, _) = Contact::add_or_lookup_ext(
context,
@@ -571,7 +582,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
grpid,
contact_id,
fingerprint,
addrs: vec![addr.to_string()],
addrs,
invitenumber,
authcode,
is_v3,
@@ -608,7 +619,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
grpid,
contact_id,
fingerprint,
addrs: vec![addr.to_string()],
addrs,
invitenumber,
authcode,
is_v3,
@@ -634,16 +645,16 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
Ok(Qr::AskVerifyContact {
contact_id,
fingerprint,
addrs: vec![addr.to_string()],
addrs,
invitenumber,
authcode,
is_v3,
})
}
} else if let Some(addr) = addr {
} else if let Some(addr) = addrs.first() {
let fingerprint = fingerprint.hex();
let (contact_id, _) =
Contact::add_or_lookup_ext(context, "", &addr, &fingerprint, Origin::UnhandledQrScan)
Contact::add_or_lookup_ext(context, "", addr, &fingerprint, Origin::UnhandledQrScan)
.await?;
let contact = Contact::get_by_id(context, contact_id).await?;

View File

@@ -139,6 +139,18 @@ pub async fn get_securejoin_qr(context: &Context, chat: Option<ChatId>) -> Resul
let self_addr = context.get_primary_self_addr().await?;
let self_addr_urlencoded = utf8_percent_encode(&self_addr, DISALLOWED_CHARACTERS).to_string();
let r_param = context
.get_published_secondary_self_addrs()
.await?
.into_iter()
.reduce(|acc, addr| {
format!(
"{acc},{}",
utf8_percent_encode(&addr, DISALLOWED_CHARACTERS)
)
})
.map_or(String::default(), |addrs| format!("&r={addrs}"));
let self_name = context
.get_config(Config::Displayname)
.await?
@@ -165,11 +177,11 @@ pub async fn get_securejoin_qr(context: &Context, chat: Option<ChatId>) -> Resul
if chat.typ == Chattype::OutBroadcast {
// For historic reansons, broadcasts currently use j instead of i for the invitenumber.
format!(
"https://i.delta.chat/#{fingerprint}&v=3&x={grpid}&j={invitenumber}&s={auth}&a={self_addr_urlencoded}&n={self_name_urlencoded}&b={chat_name_urlencoded}",
"https://i.delta.chat/#{fingerprint}&v=3&x={grpid}&j={invitenumber}&s={auth}&a={self_addr_urlencoded}{r_param}&n={self_name_urlencoded}&b={chat_name_urlencoded}",
)
} else {
format!(
"https://i.delta.chat/#{fingerprint}&v=3&x={grpid}&i={invitenumber}&s={auth}&a={self_addr_urlencoded}&n={self_name_urlencoded}&g={chat_name_urlencoded}",
"https://i.delta.chat/#{fingerprint}&v=3&x={grpid}&i={invitenumber}&s={auth}&a={self_addr_urlencoded}{r_param}&n={self_name_urlencoded}&g={chat_name_urlencoded}",
)
}
} else {
@@ -182,7 +194,7 @@ pub async fn get_securejoin_qr(context: &Context, chat: Option<ChatId>) -> Resul
context.scheduler.interrupt_smtp().await;
format!(
"https://i.delta.chat/#{fingerprint}&v=3&i={invitenumber}&s={auth}&a={self_addr_urlencoded}&n={self_name_urlencoded}",
"https://i.delta.chat/#{fingerprint}&v=3&i={invitenumber}&s={auth}&a={self_addr_urlencoded}{r_param}&n={self_name_urlencoded}",
)
};