mirror of
https://github.com/chatmail/core.git
synced 2026-05-20 07:16:31 +03:00
feat: Add unpublished flag for transports
This commit is contained in:
@@ -6784,8 +6784,8 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
* UI should update the list.
|
* UI should update the list.
|
||||||
*
|
*
|
||||||
* The event is emitted when the transports are modified on another device
|
* The event is emitted when the transports are modified on another device
|
||||||
* using the JSON-RPC calls `add_or_update_transport`, `add_transport_from_qr`, `delete_transport`
|
* using the JSON-RPC calls `add_or_update_transport`, `add_transport_from_qr`, `delete_transport`,
|
||||||
* or `set_config(configured_addr)`.
|
* `set_transport_unpublished` or `set_config(configured_addr)`.
|
||||||
*/
|
*/
|
||||||
#define DC_EVENT_TRANSPORTS_MODIFIED 2600
|
#define DC_EVENT_TRANSPORTS_MODIFIED 2600
|
||||||
|
|
||||||
|
|||||||
@@ -528,6 +528,7 @@ impl CommandApi {
|
|||||||
/// from a server encoded in a QR code.
|
/// from a server encoded in a QR code.
|
||||||
/// - [Self::list_transports()] to get a list of all configured transports.
|
/// - [Self::list_transports()] to get a list of all configured transports.
|
||||||
/// - [Self::delete_transport()] to remove a transport.
|
/// - [Self::delete_transport()] to remove a transport.
|
||||||
|
/// - [Self::set_transport_unpublished()] to set whether contacts see this transport.
|
||||||
async fn add_or_update_transport(
|
async fn add_or_update_transport(
|
||||||
&self,
|
&self,
|
||||||
account_id: u32,
|
account_id: u32,
|
||||||
@@ -571,6 +572,33 @@ impl CommandApi {
|
|||||||
ctx.delete_transport(&addr).await
|
ctx.delete_transport(&addr).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Change whether the transport is unpublished.
|
||||||
|
///
|
||||||
|
/// Unpublished transports are not advertised to contacts,
|
||||||
|
/// and self-sent messages are not sent there,
|
||||||
|
/// so that we don't cause extra messages to the corresponding inbox,
|
||||||
|
/// but can still receive messages from contacts who don't know the new relay addresses yet.
|
||||||
|
///
|
||||||
|
/// The default is true, but when updating,
|
||||||
|
/// existing secondary transports are set to unpublished,
|
||||||
|
/// so that an existing transport address doesn't suddenly get spammed with a lot of messages.
|
||||||
|
async fn set_transport_unpublished(
|
||||||
|
&self,
|
||||||
|
account_id: u32,
|
||||||
|
addr: String,
|
||||||
|
unpublished: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
ctx.set_transport_unpublished(&addr, unpublished).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether the transport is unpublished.
|
||||||
|
/// See [`Self::set_transport_unpublished`] / `setTransportUnpublished` for details.
|
||||||
|
async fn is_transport_unpublished(&self, account_id: u32, addr: String) -> Result<bool> {
|
||||||
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
ctx.is_transport_unpublished(&addr).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Signal an ongoing process to stop.
|
/// Signal an ongoing process to stop.
|
||||||
async fn stop_ongoing_process(&self, account_id: u32) -> Result<()> {
|
async fn stop_ongoing_process(&self, account_id: u32) -> Result<()> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
|||||||
@@ -827,6 +827,7 @@ impl Context {
|
|||||||
"UPDATE config SET value=? WHERE keyname='configured_addr'",
|
"UPDATE config SET value=? WHERE keyname='configured_addr'",
|
||||||
(addr,),
|
(addr,),
|
||||||
)?;
|
)?;
|
||||||
|
// TODO set as published
|
||||||
|
|
||||||
// Update the timestamp for the primary transport
|
// Update the timestamp for the primary transport
|
||||||
// so it becomes the first in `get_all_self_addrs()` list
|
// so it becomes the first in `get_all_self_addrs()` list
|
||||||
@@ -974,7 +975,23 @@ impl Context {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all published self addresses, newest first.
|
||||||
|
/// See `[Context::set_transport_unpublished]`
|
||||||
|
pub(crate) async fn get_published_self_addrs(&self) -> Result<Vec<String>> {
|
||||||
|
self.sql
|
||||||
|
.query_map_vec(
|
||||||
|
"SELECT addr FROM transports WHERE is_published=1 ORDER BY add_timestamp DESC",
|
||||||
|
(),
|
||||||
|
|row| {
|
||||||
|
let addr: String = row.get(0)?;
|
||||||
|
Ok(addr)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns all secondary self addresses.
|
/// Returns all secondary self addresses.
|
||||||
|
// TODO this function might be refactored out
|
||||||
pub(crate) async fn get_secondary_self_addrs(&self) -> Result<Vec<String>> {
|
pub(crate) async fn get_secondary_self_addrs(&self) -> Result<Vec<String>> {
|
||||||
self.sql.query_map_vec("SELECT addr FROM transports WHERE addr NOT IN (SELECT value FROM config WHERE keyname='configured_addr')", (), |row| {
|
self.sql.query_map_vec("SELECT addr FROM transports WHERE addr NOT IN (SELECT value FROM config WHERE keyname='configured_addr')", (), |row| {
|
||||||
let addr: String = row.get(0)?;
|
let addr: String = row.get(0)?;
|
||||||
@@ -982,6 +999,23 @@ impl Context {
|
|||||||
}).await
|
}).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all published secondary self addresses.
|
||||||
|
/// See `[Context::set_transport_unpublished]`
|
||||||
|
pub(crate) async fn get_published_secondary_self_addrs(&self) -> Result<Vec<String>> {
|
||||||
|
self.sql
|
||||||
|
.query_map_vec(
|
||||||
|
"SELECT addr FROM transports
|
||||||
|
WHERE is_published=1
|
||||||
|
AND addr NOT IN (SELECT value FROM config WHERE keyname='configured_addr')",
|
||||||
|
(),
|
||||||
|
|row| {
|
||||||
|
let addr: String = row.get(0)?;
|
||||||
|
Ok(addr)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the primary self address.
|
/// Returns the primary self address.
|
||||||
/// Returns an error if no self addr is configured.
|
/// Returns an error if no self addr is configured.
|
||||||
pub async fn get_primary_self_addr(&self) -> Result<String> {
|
pub async fn get_primary_self_addr(&self) -> Result<String> {
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ impl Context {
|
|||||||
/// from a server encoded in a QR code.
|
/// from a server encoded in a QR code.
|
||||||
/// - [Self::list_transports()] to get a list of all configured transports.
|
/// - [Self::list_transports()] to get a list of all configured transports.
|
||||||
/// - [Self::delete_transport()] to remove a transport.
|
/// - [Self::delete_transport()] to remove a transport.
|
||||||
|
/// - [Self::set_transport_unpublished()] to set whether contacts see this transport.
|
||||||
pub async fn add_or_update_transport(&self, param: &mut EnteredLoginParam) -> Result<()> {
|
pub async fn add_or_update_transport(&self, param: &mut EnteredLoginParam) -> Result<()> {
|
||||||
self.stop_io().await;
|
self.stop_io().await;
|
||||||
let result = self.add_transport_inner(param).await;
|
let result = self.add_transport_inner(param).await;
|
||||||
@@ -261,6 +262,39 @@ impl Context {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Change whether the transport is unpublished.
|
||||||
|
///
|
||||||
|
/// Unpublished transports are not advertised to contacts,
|
||||||
|
/// and self-sent messages are not sent there,
|
||||||
|
/// so that we don't cause extra messages to the corresponding inbox,
|
||||||
|
/// but can still receive messages from contacts who don't know the new relay addresses yet.
|
||||||
|
///
|
||||||
|
/// The default is true, but when updating,
|
||||||
|
/// existing secondary transports are set to unpublished,
|
||||||
|
/// so that an existing transport address doesn't suddenly get spammed with a lot of messages.
|
||||||
|
pub async fn set_transport_unpublished(&self, addr: &str, unpublished: bool) -> Result<()> {
|
||||||
|
// TODO check if this is the primary transport
|
||||||
|
self.sql
|
||||||
|
.execute(
|
||||||
|
"UPDATE transports SET is_published=? WHERE addr=?",
|
||||||
|
(!unpublished, addr),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
send_sync_transports(self).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether the transport is unpublished.
|
||||||
|
/// See [`Self::set_transport_unpublished`] for details.
|
||||||
|
pub async fn is_transport_unpublished(&self, addr: &str) -> Result<bool> {
|
||||||
|
let published: bool = self
|
||||||
|
.sql
|
||||||
|
.query_get_value("SELECT is_published FROM transports WHERE addr=?", (addr,))
|
||||||
|
.await?
|
||||||
|
.context("is_published is not supposed to be NULL")?;
|
||||||
|
Ok(!published)
|
||||||
|
}
|
||||||
|
|
||||||
async fn inner_configure(&self, param: &EnteredLoginParam) -> Result<()> {
|
async fn inner_configure(&self, param: &EnteredLoginParam) -> Result<()> {
|
||||||
info!(self, "Configure ...");
|
info!(self, "Configure ...");
|
||||||
|
|
||||||
@@ -627,7 +661,7 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Option<&'
|
|||||||
let provider = configured_param.provider;
|
let provider = configured_param.provider;
|
||||||
configured_param
|
configured_param
|
||||||
.clone()
|
.clone()
|
||||||
.save_to_transports_table(ctx, param, time())
|
.save_to_transports_table(ctx, param, time(), true)
|
||||||
.await?;
|
.await?;
|
||||||
send_sync_transports(ctx).await?;
|
send_sync_transports(ctx).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ pub(crate) async fn load_self_public_key_opt(context: &Context) -> Result<Option
|
|||||||
.await?
|
.await?
|
||||||
.context("No transports configured")?;
|
.context("No transports configured")?;
|
||||||
let addr = context.get_primary_self_addr().await?;
|
let addr = context.get_primary_self_addr().await?;
|
||||||
let all_addrs = context.get_all_self_addrs().await?.join(",");
|
let all_addrs = context.get_published_self_addrs().await?.join(",");
|
||||||
let signed_public_key =
|
let signed_public_key =
|
||||||
secret_key_to_public_key(context, signed_secret_key, timestamp, &addr, &all_addrs)?;
|
secret_key_to_public_key(context, signed_secret_key, timestamp, &addr, &all_addrs)?;
|
||||||
*lock = Some(signed_public_key.clone());
|
*lock = Some(signed_public_key.clone());
|
||||||
|
|||||||
@@ -706,7 +706,7 @@ pub(crate) async fn add_self_recipients(
|
|||||||
// them. Normally the user should have a non-chatmail primary transport to send unencrypted
|
// them. Normally the user should have a non-chatmail primary transport to send unencrypted
|
||||||
// messages.
|
// messages.
|
||||||
if encrypted {
|
if encrypted {
|
||||||
for addr in context.get_secondary_self_addrs().await? {
|
for addr in context.get_published_secondary_self_addrs().await? {
|
||||||
recipients.push(addr);
|
recipients.push(addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2343,6 +2343,26 @@ ALTER TABLE contacts ADD COLUMN name_normalized TEXT;
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add an `is_published` flag to transports.
|
||||||
|
// Unpublished transports are not advertised to contacts,
|
||||||
|
// and self-sent messages are not sent there,
|
||||||
|
// so that we don't cause extra messages to the corresponding inbox,
|
||||||
|
// but can still receive messages from contacts who don't know the new relay addresses yet.
|
||||||
|
// The default is true, but when updating,
|
||||||
|
// existing secondary transports are set to unpublished,
|
||||||
|
// so that an existing transport address doesn't suddenly get spammed with a lot of messages.
|
||||||
|
inc_and_check(&mut migration_version, 149)?;
|
||||||
|
if dbversion < migration_version {
|
||||||
|
sql.execute_migration(
|
||||||
|
"ALTER TABLE transports ADD COLUMN is_published INTEGER DEFAULT 1 NOT NULL;
|
||||||
|
UPDATE transports SET is_published=0 WHERE addr!=(
|
||||||
|
SELECT value FROM config WHERE keyname='configured_addr'
|
||||||
|
)",
|
||||||
|
migration_version,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
let new_version = sql
|
let new_version = sql
|
||||||
.get_raw_config_int(VERSION_CFG)
|
.get_raw_config_int(VERSION_CFG)
|
||||||
.await?
|
.await?
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ pub(crate) struct TransportData {
|
|||||||
|
|
||||||
/// Timestamp of when the transport was last time (re)configured.
|
/// Timestamp of when the transport was last time (re)configured.
|
||||||
pub(crate) timestamp: i64,
|
pub(crate) timestamp: i64,
|
||||||
|
|
||||||
|
/// Whether the transport is published.
|
||||||
|
/// See [`Context::set_transport_unpublished`] for details.
|
||||||
|
pub(crate) is_published: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
|||||||
@@ -561,8 +561,16 @@ impl ConfiguredLoginParam {
|
|||||||
context: &Context,
|
context: &Context,
|
||||||
entered_param: &EnteredLoginParam,
|
entered_param: &EnteredLoginParam,
|
||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
|
is_published: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
save_transport(context, entered_param, &self.into(), timestamp).await?;
|
save_transport(
|
||||||
|
context,
|
||||||
|
entered_param,
|
||||||
|
&self.into(),
|
||||||
|
timestamp,
|
||||||
|
is_published,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,6 +636,7 @@ pub(crate) async fn save_transport(
|
|||||||
entered_param: &EnteredLoginParam,
|
entered_param: &EnteredLoginParam,
|
||||||
configured: &ConfiguredLoginParamJson,
|
configured: &ConfiguredLoginParamJson,
|
||||||
add_timestamp: i64,
|
add_timestamp: i64,
|
||||||
|
is_published: bool,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
let addr = addr_normalize(&configured.addr);
|
let addr = addr_normalize(&configured.addr);
|
||||||
let configured_addr = context.get_config(Config::ConfiguredAddr).await?;
|
let configured_addr = context.get_config(Config::ConfiguredAddr).await?;
|
||||||
@@ -635,20 +644,23 @@ pub(crate) async fn save_transport(
|
|||||||
let mut modified = context
|
let mut modified = context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
"INSERT INTO transports (addr, entered_param, configured_param, add_timestamp)
|
"INSERT INTO transports (addr, entered_param, configured_param, add_timestamp, is_published)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
ON CONFLICT (addr)
|
ON CONFLICT (addr)
|
||||||
DO UPDATE SET entered_param=excluded.entered_param,
|
DO UPDATE SET entered_param=excluded.entered_param,
|
||||||
configured_param=excluded.configured_param,
|
configured_param=excluded.configured_param,
|
||||||
add_timestamp=excluded.add_timestamp
|
add_timestamp=excluded.add_timestamp,
|
||||||
|
is_published=excluded.is_published,
|
||||||
WHERE entered_param != excluded.entered_param
|
WHERE entered_param != excluded.entered_param
|
||||||
OR configured_param != excluded.configured_param
|
OR configured_param != excluded.configured_param
|
||||||
OR add_timestamp < excluded.add_timestamp",
|
OR add_timestamp < excluded.add_timestamp
|
||||||
|
OR is_published != excluded.is_published",
|
||||||
(
|
(
|
||||||
&addr,
|
&addr,
|
||||||
serde_json::to_string(entered_param)?,
|
serde_json::to_string(entered_param)?,
|
||||||
serde_json::to_string(configured)?,
|
serde_json::to_string(configured)?,
|
||||||
add_timestamp,
|
add_timestamp,
|
||||||
|
is_published,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
@@ -685,7 +697,7 @@ pub(crate) async fn send_sync_transports(context: &Context) -> Result<()> {
|
|||||||
let transports = context
|
let transports = context
|
||||||
.sql
|
.sql
|
||||||
.query_map_vec(
|
.query_map_vec(
|
||||||
"SELECT entered_param, configured_param, add_timestamp
|
"SELECT entered_param, configured_param, add_timestamp, is_published
|
||||||
FROM transports WHERE id>1",
|
FROM transports WHERE id>1",
|
||||||
(),
|
(),
|
||||||
|row| {
|
|row| {
|
||||||
@@ -694,10 +706,12 @@ pub(crate) async fn send_sync_transports(context: &Context) -> Result<()> {
|
|||||||
let configured_json: String = row.get(1)?;
|
let configured_json: String = row.get(1)?;
|
||||||
let configured: ConfiguredLoginParamJson = serde_json::from_str(&configured_json)?;
|
let configured: ConfiguredLoginParamJson = serde_json::from_str(&configured_json)?;
|
||||||
let timestamp: i64 = row.get(2)?;
|
let timestamp: i64 = row.get(2)?;
|
||||||
|
let is_published: bool = row.get(3)?;
|
||||||
Ok(TransportData {
|
Ok(TransportData {
|
||||||
configured,
|
configured,
|
||||||
entered,
|
entered,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
is_published,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -736,9 +750,10 @@ pub(crate) async fn sync_transports(
|
|||||||
configured,
|
configured,
|
||||||
entered,
|
entered,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
is_published,
|
||||||
} in transports
|
} in transports
|
||||||
{
|
{
|
||||||
modified |= save_transport(context, entered, configured, *timestamp).await?;
|
modified |= save_transport(context, entered, configured, *timestamp, *is_published).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
context
|
context
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ async fn test_save_load_login_param() -> Result<()> {
|
|||||||
|
|
||||||
param
|
param
|
||||||
.clone()
|
.clone()
|
||||||
.save_to_transports_table(&t, &EnteredLoginParam::default(), time())
|
.save_to_transports_table(&t, &EnteredLoginParam::default(), time(), true)
|
||||||
.await?;
|
.await?;
|
||||||
let expected_param = r#"{"addr":"alice@example.org","imap":[{"connection":{"host":"imap.example.com","port":123,"security":"Starttls"},"user":"alice"}],"imap_user":"","imap_password":"foo","smtp":[{"connection":{"host":"smtp.example.com","port":456,"security":"Tls"},"user":"alice@example.org"}],"smtp_user":"","smtp_password":"bar","provider_id":null,"certificate_checks":"Strict","oauth2":false}"#;
|
let expected_param = r#"{"addr":"alice@example.org","imap":[{"connection":{"host":"imap.example.com","port":123,"security":"Starttls"},"user":"alice"}],"imap_user":"","imap_password":"foo","smtp":[{"connection":{"host":"smtp.example.com","port":456,"security":"Tls"},"user":"alice@example.org"}],"smtp_user":"","smtp_password":"bar","provider_id":null,"certificate_checks":"Strict","oauth2":false}"#;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -265,7 +265,7 @@ async fn test_empty_server_list() -> Result<()> {
|
|||||||
certificate_checks: ConfiguredCertificateChecks::Automatic,
|
certificate_checks: ConfiguredCertificateChecks::Automatic,
|
||||||
oauth2: false,
|
oauth2: false,
|
||||||
}
|
}
|
||||||
.save_to_transports_table(&t, &EnteredLoginParam::default(), time())
|
.save_to_transports_table(&t, &EnteredLoginParam::default(), time(), true)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let (_transport_id, loaded) = ConfiguredLoginParam::load(&t).await?.unwrap();
|
let (_transport_id, loaded) = ConfiguredLoginParam::load(&t).await?.unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user