From cf9607b15e1ad5e6fa908d1b71504b3fdd961367 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 28 Aug 2026 13:20:20 +0200 Subject: [PATCH 1/4] [Fix] Stability --- Cargo.toml | 1 - src/db/iota_repo.rs | 8 ++++ src/identity.rs | 65 +++++++++++++---------------- src/main.rs | 16 +------ src/transport/omikron_connection.rs | 19 ++++++++- 5 files changed, 55 insertions(+), 54 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 395ad06..b7d4164 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,4 +31,3 @@ uuid = { version = "1.24.0", features = ["v4", "v7"] } thiserror = "2.0.19" serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.151" -zeroize = "1.9" diff --git a/src/db/iota_repo.rs b/src/db/iota_repo.rs index fb50432..e413684 100644 --- a/src/db/iota_repo.rs +++ b/src/db/iota_repo.rs @@ -54,6 +54,14 @@ pub async fn register_complete_iota(id: IotaId, public_key: PublicKeyBundle) -> Ok(()) } +pub async fn change_iota_key(id: IotaId, key: PublicKeyBundle) -> Result<()> { + sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") + .bind(key.try_as_bytes()?) + .bind(id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} pub async fn delete_iota(id: IotaId) -> Result<()> { sqlx::query("DELETE FROM iotas WHERE id = ?") .bind(id.0) diff --git a/src/identity.rs b/src/identity.rs index a7a831d..0fb416d 100644 --- a/src/identity.rs +++ b/src/identity.rs @@ -1,7 +1,8 @@ use crate::error::{IdentityError, Result}; use mtp::crypto::{Keyring, PublicKeyBundle}; use mtp::files::{ - FileError, load_keyring, load_public_key_bundle, save_keyring, save_public_key_bundle, + FileError, load_keyring_raw, load_public_key_bundle, save_keyring_raw, + save_public_key_bundle, }; use std::{ fs, @@ -22,25 +23,20 @@ pub struct OmegaIdentity { static PUBLIC_BUNDLE_TEMP_COUNTER: AtomicU64 = AtomicU64::new(0); impl OmegaIdentity { - pub fn load_or_create(passphrase: &[u8]) -> Result { - Self::load_or_create_at( - Path::new(KEYRING_PATH), - Path::new(PUBLIC_KEY_PATH), - passphrase, - ) + pub fn load_or_create() -> Result { + Self::load_or_create_at(Path::new(KEYRING_PATH), Path::new(PUBLIC_KEY_PATH)) } pub(crate) fn load_or_create_at( keyring_path: impl AsRef, public_key_path: impl AsRef, - passphrase: &[u8], ) -> Result { let keyring_path = keyring_path.as_ref(); let public_key_path = public_key_path.as_ref(); - let keyring = match load_keyring(keyring_path, passphrase) { + let keyring = match load_keyring_raw(keyring_path) { Ok(keyring) => keyring, Err(FileError::Io(error)) if error.kind() == std::io::ErrorKind::NotFound => { - return Self::create_at(keyring_path, public_key_path, passphrase); + return Self::create_at(keyring_path, public_key_path); } Err(error) => { return Err(IdentityError::Storage { @@ -72,9 +68,9 @@ impl OmegaIdentity { Ok(identity) } - fn create_at(keyring_path: &Path, public_key_path: &Path, passphrase: &[u8]) -> Result { + fn create_at(keyring_path: &Path, public_key_path: &Path) -> Result { let keyring = Keyring::generate(); - save_keyring(&keyring, keyring_path, passphrase).map_err(|error| { + save_keyring_raw(&keyring, keyring_path).map_err(|error| { IdentityError::Storage { path: keyring_path.to_path_buf(), source: error, @@ -188,9 +184,7 @@ mod tests { let directory = test_directory(); let keyring_path = directory.join("omega.mk"); let public_key_path = directory.join("omega.mpkb"); - let passphrase = b"test-passphrase"; - - let first = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + let first = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path) .expect("create identity"); let first_bundle = first.public_key_bundle().try_as_bytes().expect("bundle"); @@ -199,9 +193,8 @@ mod tests { assert_eq!(&keyring_bytes[..4], b"MTMK"); assert_eq!(&bundle_bytes[..4], b"MPKB"); - let restarted = - OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) - .expect("reload identity"); + let restarted = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path) + .expect("reload identity"); assert_eq!( restarted .public_key_bundle() @@ -227,8 +220,7 @@ mod tests { let public_key_path = directory.join("omega.mpkb"); fs::write(&keyring_path, b"not-a-keyring").expect("write invalid keyring"); - let result = - OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, b"test-passphrase"); + let result = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path); assert!(result.is_err()); assert!(!public_key_path.exists()); @@ -240,16 +232,13 @@ mod tests { let directory = test_directory(); let keyring_path = directory.join("omega.mk"); let public_key_path = directory.join("omega.mpkb"); - let passphrase = b"test-passphrase"; - - OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path) .expect("create identity"); let original_keyring = fs::read(&keyring_path).expect("read keyring"); fs::remove_file(&public_key_path).expect("remove bundle"); - let repaired = - OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) - .expect("repair bundle"); + let repaired = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path) + .expect("repair bundle"); assert_eq!( fs::read(&keyring_path).expect("read keyring"), @@ -271,16 +260,14 @@ mod tests { let directory = test_directory(); let keyring_path = directory.join("omega.mk"); let public_key_path = directory.join("omega.mpkb"); - let passphrase = b"test-passphrase"; - - OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path) .expect("create identity"); let other_keyring = Keyring::generate(); save_public_key_bundle(&other_keyring.public_key_bundle(), &public_key_path) .expect("save mismatched bundle"); assert!(matches!( - OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase), + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path), Err(crate::OmegaError::Identity( IdentityError::PublicBundleMismatch { .. } )) @@ -290,24 +277,28 @@ mod tests { } #[test] - fn wrong_passphrase_does_not_replace_existing_keyring() { + fn existing_raw_keyring_is_reloaded_without_a_passphrase() { let directory = test_directory(); let keyring_path = directory.join("omega.mk"); let public_key_path = directory.join("omega.mpkb"); - let passphrase = b"test-passphrase"; - OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + let first = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path) .expect("create identity"); let original_keyring = fs::read(&keyring_path).expect("read keyring"); - assert!( - OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, b"wrong-passphrase") - .is_err() - ); + let reloaded = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path) + .expect("reload identity"); assert_eq!( fs::read(&keyring_path).expect("read keyring"), original_keyring ); + assert_eq!( + reloaded + .public_key_bundle() + .try_as_bytes() + .expect("bundle"), + first.public_key_bundle().try_as_bytes().expect("bundle") + ); fs::remove_dir_all(directory).expect("remove test directory"); } diff --git a/src/main.rs b/src/main.rs index e04238c..d302ff6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,6 @@ use std::env; use std::path::Path; use std::time::Duration; use tokio::time::interval; -use zeroize::Zeroizing; #[tokio::main] async fn main() { @@ -38,18 +37,6 @@ async fn main() { log_in!("Incoming messages"); log_out!("Outgoing messages"); - let identity_secret = match env::var("OMEGA_IDENTITY_SECRET") { - Ok(secret) if !secret.is_empty() => secret, - Ok(_) => { - log!("[FATAL] OMEGA_IDENTITY_SECRET must not be empty"); - return; - } - Err(error) => { - log!("[FATAL] Unable to load OMEGA_IDENTITY_SECRET: {}", error); - return; - } - }; - let identity_secret = Zeroizing::new(identity_secret); let config = match OmegaConfig::from_env() { Ok(config) => config, Err(error) => { @@ -61,14 +48,13 @@ async fn main() { log!("[FATAL] Omega rate-limit configuration was initialized more than once"); return; } - let identity = match identity::OmegaIdentity::load_or_create(identity_secret.as_bytes()) { + let identity = match identity::OmegaIdentity::load_or_create() { Ok(identity) => identity, Err(error) => { log!("[FATAL] Omega identity initialization failed: {}", error); return; } }; - drop(identity_secret); let state = OmegaState::new(identity, config); log!("Started"); diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 8c536c7..1194a2d 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -764,8 +764,9 @@ pub async fn start(port: u16, state: Arc) -> Result<(), Box Date: Fri, 28 Aug 2026 16:21:11 +0200 Subject: [PATCH 2/4] [Add] Replyjumps --- mtp-type-maps | 2 +- src/transport/omikron_connection.rs | 8 ++++---- src/transport/relay_router.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mtp-type-maps b/mtp-type-maps index 486541b..f3c5037 160000 --- a/mtp-type-maps +++ b/mtp-type-maps @@ -1 +1 @@ -Subproject commit 486541b9483356ff49ff3ec7016f87d3ecbeaa0e +Subproject commit f3c5037b0a099ae5389486eec77471bd5addab4a diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 1194a2d..da4a95e 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -394,9 +394,7 @@ impl OmikronConnection { let request_id = value.id(); let result = crate::transport::relay_router::route_from_omikron(id, value).await; let response = match &result { - Ok(()) => request_id.map(|request_id| { - CommunicationValue::new(CommunicationType::Success).with_id(request_id) - }), + Ok(response) => request_id.map(|request_id| response.clone().with_id(request_id)), Err(error) => { log_err!(id, PrintType::Omega, "Relay routing failed: {}", error); request_id.map(|request_id| { @@ -417,7 +415,9 @@ impl OmikronConnection { send_error ); } - return result.map_err(|error| crate::error::OmegaError::Transport(error.to_string())); + return result + .map(|_| ()) + .map_err(|error| crate::error::OmegaError::Transport(error.to_string())); } match value.get_comm_type_enum() { Some(CommunicationType::ShortenLink) => { diff --git a/src/transport/relay_router.rs b/src/transport/relay_router.rs index 0cbd1f2..0a76ea7 100644 --- a/src/transport/relay_router.rs +++ b/src/transport/relay_router.rs @@ -82,7 +82,7 @@ pub fn error_response_type(error: &RelayRouteError) -> CommunicationType { pub async fn route_from_omikron( source_omikron_id: i64, frame: CommunicationValue, -) -> Result<(), RelayRouteError> { +) -> Result { let frame = ensure_relay_frame_id(frame); if !frame.is_type(CommunicationType::Relay) { return Err(RelayRouteError::Relay(RelayError::NotRelay)); @@ -154,7 +154,7 @@ pub async fn route_from_omikron( RelayRouteError::Send(error.to_string()) })?; if response.is_type(CommunicationType::Success) { - Ok(()) + Ok(response) } else { Err(RelayRouteError::Send(format!( "destination Omikron rejected the Relay with {}", From ed10028808e89601aee18453676f0fd9b8feb700 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 29 Aug 2026 12:49:03 +0200 Subject: [PATCH 3/4] [Fix] Connectivity --- mtp-type-maps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mtp-type-maps b/mtp-type-maps index f3c5037..f4e45aa 160000 --- a/mtp-type-maps +++ b/mtp-type-maps @@ -1 +1 @@ -Subproject commit f3c5037b0a099ae5389486eec77471bd5addab4a +Subproject commit f4e45aa3a3ad0e3c3a257f66857b904a1af7901c From 8e709513c0d79438662ad3bbe78fa825f2608c5f Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 30 Aug 2026 19:18:03 +0200 Subject: [PATCH 4/4] [Fix] Connections --- Cargo.lock | 550 +++++------------------- Cargo.toml | 11 +- README.md | 2 +- migrations/007_iota_snapshot_outbox.sql | 4 + src/db/iota_repo.rs | 10 +- src/db/user_repo.rs | 113 +++-- src/identity.rs | 22 +- src/main.rs | 3 + src/server/api.rs | 59 ++- src/server/index.rs | 12 +- src/server/mod.rs | 26 ++ src/server/web.rs | 9 +- src/transport/connection.rs | 2 - src/transport/handlers/account.rs | 9 +- src/transport/handlers/presence.rs | 9 + src/transport/handlers/register.rs | 11 + src/transport/handlers/user_data.rs | 46 -- src/transport/omikron_connection.rs | 3 - src/transport/omikron_manager.rs | 94 +++- 19 files changed, 382 insertions(+), 613 deletions(-) create mode 100644 migrations/007_iota_snapshot_outbox.sql diff --git a/Cargo.lock b/Cargo.lock index d5bf59a..f2ea815 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -252,12 +252,12 @@ dependencies = [ [[package]] name = "chacha20" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "rand_core 0.10.1", ] @@ -310,12 +310,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "const-oid" version = "0.10.2" @@ -349,9 +343,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ "libc", ] @@ -439,7 +433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "curve25519-dalek-derive", "digest 0.11.3", "fiat-crypto 0.3.0", @@ -479,25 +473,14 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid 0.9.6", - "pem-rfc7468 0.7.0", - "zeroize", -] - [[package]] name = "der" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" dependencies = [ - "const-oid 0.10.2", - "pem-rfc7468 1.0.0", + "const-oid", + "pem-rfc7468", "zeroize", ] @@ -528,7 +511,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid 0.9.6", "crypto-common 0.1.7", "subtle", ] @@ -540,7 +522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.1", - "const-oid 0.10.2", + "const-oid", "crypto-common 0.2.2", "ctutils", ] @@ -580,8 +562,8 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" dependencies = [ - "pkcs8 0.11.0", - "signature 3.0.0", + "pkcs8", + "signature", ] [[package]] @@ -594,7 +576,7 @@ dependencies = [ "ed25519", "serde", "sha2 0.11.0", - "signature 3.0.0", + "signature", "subtle", "zeroize", ] @@ -626,13 +608,12 @@ dependencies = [ [[package]] name = "etcetera" -version = "0.8.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" dependencies = [ "cfg-if", - "home", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -651,7 +632,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef975e30683b2d965054bb0a836f8973857c4ebf6acf274fe46617cd285060d8" dependencies = [ - "foldhash 0.2.0", + "foldhash", "libm", "portable-atomic", "siphasher", @@ -683,9 +664,9 @@ checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flume" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" dependencies = [ "futures-core", "futures-sink", @@ -698,12 +679,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -944,13 +919,13 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.1.5", + "foldhash", ] [[package]] @@ -961,11 +936,11 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -980,31 +955,13 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac 0.12.1", -] - [[package]] name = "hkdf" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" dependencies = [ - "hmac 0.13.0", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", + "hmac", ] [[package]] @@ -1016,15 +973,6 @@ dependencies = [ "digest 0.11.3", ] -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "httlib-huffman" version = "0.3.4" @@ -1088,9 +1036,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ "atomic-waker", "bytes", @@ -1227,9 +1175,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -1336,7 +1284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", ] [[package]] @@ -1344,9 +1292,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "libc" @@ -1360,23 +1305,11 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libredox" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" -dependencies = [ - "bitflags", - "libc", - "plain", - "redox_syscall 0.9.3", -] - [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" dependencies = [ "pkg-config", "vcpkg", @@ -1411,12 +1344,12 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "md-5" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest 0.10.7", + "digest 0.11.3", ] [[package]] @@ -1448,14 +1381,14 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d" dependencies = [ - "const-oid 0.10.2", + "const-oid", "crypto-common 0.2.2", "ctutils", "hybrid-array", "module-lattice", - "pkcs8 0.11.0", + "pkcs8", "shake", - "signature 3.0.0", + "signature", ] [[package]] @@ -1498,7 +1431,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git#f6a8b464e0daae6c7753208f3d8601d21cd47ab2" dependencies = [ "mtp-client", "mtp-codec", @@ -1514,37 +1447,36 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git#f6a8b464e0daae6c7753208f3d8601d21cd47ab2" dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.10.2", + "rand", "tokio", ] [[package]] name = "mtp-codec" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git#f6a8b464e0daae6c7753208f3d8601d21cd47ab2" dependencies = [ "base64 0.23.1", "byteorder", "mtp-common", "mtp-crypto", "mtp-type-map", - "rand 0.10.2", + "rand", "thiserror 2.0.20", ] [[package]] name = "mtp-common" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git#f6a8b464e0daae6c7753208f3d8601d21cd47ab2" dependencies = [ "quinn", - "rustls", "thiserror 2.0.20", "wtransport", ] @@ -1552,17 +1484,17 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git#f6a8b464e0daae6c7753208f3d8601d21cd47ab2" dependencies = [ "argon2", "base64 0.22.1", "chacha20poly1305", "ed25519-dalek", "getrandom 0.4.3", - "hkdf 0.13.0", + "hkdf", "ml-dsa", "mlkem-tls", - "rand 0.10.2", + "rand", "rand_core 0.6.4", "rustls", "serde", @@ -1575,10 +1507,10 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git#f6a8b464e0daae6c7753208f3d8601d21cd47ab2" dependencies = [ "mtp-crypto", - "rand 0.10.2", + "rand", "thiserror 2.0.20", "zeroize", ] @@ -1586,13 +1518,13 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git#f6a8b464e0daae6c7753208f3d8601d21cd47ab2" dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.10.2", + "rand", "thiserror 2.0.20", "tokio", "tracing", @@ -1602,13 +1534,13 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git#f6a8b464e0daae6c7753208f3d8601d21cd47ab2" dependencies = [ "async-trait", "mtp-codec", "mtp-common", "mtp-crypto", - "rand 0.10.2", + "rand", "rcgen", "rustls", "rustls-native-certs", @@ -1622,7 +1554,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git#f6a8b464e0daae6c7753208f3d8601d21cd47ab2" dependencies = [ "serde", "serde_yaml", @@ -1631,7 +1563,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" +source = "git+https://git.methanium.net/Methanium/mtp.git#f6a8b464e0daae6c7753208f3d8601d21cd47ab2" dependencies = [ "async-trait", "bytes", @@ -1676,22 +1608,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.8", - "smallvec", - "zeroize", -] - [[package]] name = "num-conv" version = "0.2.2" @@ -1707,16 +1623,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1724,7 +1630,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -1747,14 +1652,14 @@ name = "omega" version = "0.1.0" dependencies = [ "ansi_term", - "base64 0.22.1", + "base64 0.23.1", "bytes", "dashmap", "dotenv", "http", "mtp", "once_cell", - "rand 0.10.2", + "rand", "rustls", "serde", "serde_json", @@ -1763,7 +1668,6 @@ dependencies = [ "tokio", "tokio-util", "uuid", - "zeroize", ] [[package]] @@ -1808,7 +1712,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] @@ -1835,12 +1739,13 @@ dependencies = [ ] [[package]] -name = "pem-rfc7468" -version = "0.7.0" +name = "pem" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +checksum = "d354a98a3d1251555de99e8fdd8afda05573c31b82f59063a7b0a29b5527f120" dependencies = [ - "base64ct", + "base64 0.23.1", + "serde_core", ] [[package]] @@ -1864,35 +1769,14 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der 0.7.10", - "pkcs8 0.10.2", - "spki 0.7.3", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der 0.7.10", - "spki 0.7.3", -] - [[package]] name = "pkcs8" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ - "der 0.8.1", - "spki 0.8.0", + "der", + "spki", ] [[package]] @@ -1901,12 +1785,6 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "poly1305" version = "0.8.0" @@ -1939,15 +1817,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "proc-macro2" version = "1.0.107" @@ -1989,7 +1858,7 @@ dependencies = [ "fastbloom", "getrandom 0.4.3", "lru-slab", - "rand 0.10.2", + "rand", "rand_pcg", "ring", "rustc-hash", @@ -2032,38 +1901,17 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ - "chacha20 0.10.1", + "chacha20 0.10.2", "getrandom 0.4.3", "rand_core 0.10.1", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - [[package]] name = "rand_core" version = "0.6.4" @@ -2090,12 +1938,12 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.14.9" +version = "0.14.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091e7a8e7d86e6feb87a27ce8e2cba29d49eff9507afeebefab7eeb2ca667fb4" +checksum = "8774e05a7d0de114588e6a28fe7e71694b82614ed569d86d8b389dfbc98b8ad8" dependencies = [ "aws-lc-rs", - "pem", + "pem 4.0.0", "ring", "rustls-pki-types", "time", @@ -2112,15 +1960,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_syscall" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d678d17679829e73d371e96880897e98fee2ded7acc0a50bdf8af2affa4b2fe5" -dependencies = [ - "bitflags", -] - [[package]] name = "ring" version = "0.17.14" @@ -2135,26 +1974,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid 0.9.6", - "digest 0.10.7", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8 0.10.2", - "rand_core 0.6.4", - "signature 2.2.0", - "spki 0.7.3", - "subtle", - "zeroize", -] - [[package]] name = "rustc-hash" version = "2.1.3" @@ -2364,18 +2183,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -2391,13 +2198,13 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.7" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -2418,7 +2225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "digest 0.11.3", ] @@ -2459,16 +2266,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - [[package]] name = "signature" version = "3.0.0" @@ -2535,16 +2332,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der 0.7.10", -] - [[package]] name = "spki" version = "0.8.0" @@ -2552,7 +2339,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ "base64ct", - "der 0.8.1", + "der", ] [[package]] @@ -2563,9 +2350,9 @@ checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" [[package]] name = "sqlx" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2576,12 +2363,13 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" dependencies = [ "base64 0.22.1", "bytes", + "cfg-if", "crc", "crossbeam-queue", "either", @@ -2590,12 +2378,11 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "hashlink", "indexmap", "log", "memchr", - "once_cell", "percent-encoding", "serde", "serde_json", @@ -2610,9 +2397,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf" dependencies = [ "proc-macro2", "quote", @@ -2623,15 +2410,15 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3" dependencies = [ + "cfg-if", "dotenvy", "either", "heck", "hex", - "once_cell", "proc-macro2", "quote", "serde", @@ -2642,57 +2429,42 @@ dependencies = [ "sqlx-postgres", "sqlx-sqlite", "syn 2.0.119", + "thiserror 2.0.20", "tokio", "url", ] [[package]] name = "sqlx-mysql" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" dependencies = [ - "atoi", - "base64 0.22.1", "bitflags", "byteorder", "bytes", "crc", - "digest 0.10.7", + "digest 0.11.3", "dotenvy", "either", - "futures-channel", "futures-core", - "futures-io", "futures-util", "generic-array", - "hex", - "hkdf 0.12.4", - "hmac 0.12.1", - "itoa", "log", - "md-5", - "memchr", - "once_cell", "percent-encoding", - "rand 0.8.8", - "rsa", "serde", "sha1", - "sha2 0.10.9", - "smallvec", + "sha2 0.11.0", "sqlx-core", - "stringprep", "thiserror 2.0.20", "tracing", - "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" dependencies = [ "atoi", "base64 0.22.1", @@ -2705,18 +2477,16 @@ dependencies = [ "futures-core", "futures-util", "hex", - "hkdf 0.12.4", - "hmac 0.12.1", - "home", + "hkdf", + "hmac", "itoa", "log", "md-5", "memchr", - "once_cell", - "rand 0.8.8", + "rand", "serde", "serde_json", - "sha2 0.10.9", + "sha2 0.11.0", "smallvec", "sqlx-core", "stringprep", @@ -2727,12 +2497,13 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", + "form_urlencoded", "futures-channel", "futures-core", "futures-executor", @@ -2742,7 +2513,6 @@ dependencies = [ "log", "percent-encoding", "serde", - "serde_urlencoded", "sqlx-core", "thiserror 2.0.20", "tracing", @@ -3114,12 +2884,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.127" @@ -3186,13 +2950,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.6.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" [[package]] name = "winapi" @@ -3231,22 +2991,13 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -3258,67 +3009,34 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3331,48 +3049,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3392,7 +3086,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4273ce3157a3262a68665f8d3f20a0ac0c5b8a69ffd67f05ae986832ebec036" dependencies = [ "bytes", - "pem", + "pem 3.0.6", "quinn", "rcgen", "rustls", @@ -3485,26 +3179,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zerocopy" -version = "0.8.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "zerofrom" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index b7d4164..d5f04b3 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,27 +7,28 @@ edition = "2024" mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = [ "crypto", "files", + "raw", "web-server", ] } ansi_term = "0.12.1" -base64 = "0.22.1" +base64 = "0.23.1" bytes = "1" dashmap = "6.2.1" dotenv = "0.15.0" http = "1" once_cell = "1.21.4" rand = "0.10.2" -rustls = { version = "0.23.42", default-features = false, features = [ +rustls = { version = "0.23.43", default-features = false, features = [ "std", "tls12", "aws-lc-rs", "prefer-post-quantum", ] } -sqlx = { version = "0.8.6", features = ["mysql", "runtime-tokio", "migrate"] } +sqlx = { version = "0.9.0", features = ["mysql", "runtime-tokio", "migrate"] } tokio = { version = "*", features = ["full"] } tokio-util = { version = "0.7.19", features = ["rt"] } -uuid = { version = "1.24.0", features = ["v4", "v7"] } -thiserror = "2.0.19" +uuid = { version = "1.26.0", features = ["v4", "v7"] } +thiserror = "2.0.20" serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.151" diff --git a/README.md b/README.md index 2c05741..f60aecf 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Omega The Omega is Tensamin's central Server. It maintains the centralized user Registry & manages Omikron useage. -Omega requires the non-empty `OMEGA_IDENTITY_SECRET` environment variable at startup. On first start it creates the protected private identity at `./omega.mk` and the matching public bundle at `./omega.mpkb`. Existing identity files are loaded fail-closed, so a malformed keyring or mismatched public bundle stops startup. A missing public bundle is rebuilt from a valid private keyring without generating a new identity. +On first start Omega creates an unencrypted private keyring at `./omega.mk` and the matching public bundle at `./omega.mpkb`. Existing identity files are loaded fail-closed, so a malformed keyring or mismatched public bundle stops startup. A missing public bundle is rebuilt from a valid private keyring without generating a new identity. ## MTP routing contract diff --git a/migrations/007_iota_snapshot_outbox.sql b/migrations/007_iota_snapshot_outbox.sql new file mode 100644 index 0000000..ba9a972 --- /dev/null +++ b/migrations/007_iota_snapshot_outbox.sql @@ -0,0 +1,4 @@ +CREATE TABLE iota_snapshot_outbox ( + iota_id BIGINT NOT NULL PRIMARY KEY, + updated_at DATETIME NOT NULL +); diff --git a/src/db/iota_repo.rs b/src/db/iota_repo.rs index e413684..5b5101d 100644 --- a/src/db/iota_repo.rs +++ b/src/db/iota_repo.rs @@ -22,7 +22,7 @@ pub async fn get_iota_by_id(id: IotaId) -> Result { pub async fn create_new_iota(public_key: PublicKeyBundle) -> Result { for _ in 0..16 { - let id = crate::db::user_repo::get_register_id().await?; + let id = crate::db::user_repo::generate_protocol_id(); let iota_id = IotaId::from(id.0); match register_complete_iota(iota_id, public_key.clone()).await { Ok(()) => return Ok(iota_id), @@ -54,14 +54,6 @@ pub async fn register_complete_iota(id: IotaId, public_key: PublicKeyBundle) -> Ok(()) } -pub async fn change_iota_key(id: IotaId, key: PublicKeyBundle) -> Result<()> { - sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") - .bind(key.try_as_bytes()?) - .bind(id.0) - .execute(&pool().await?) - .await?; - Ok(()) -} pub async fn delete_iota(id: IotaId) -> Result<()> { sqlx::query("DELETE FROM iotas WHERE id = ?") .bind(id.0) diff --git a/src/db/user_repo.rs b/src/db/user_repo.rs index 0d10d4c..76b7cdf 100644 --- a/src/db/user_repo.rs +++ b/src/db/user_repo.rs @@ -11,22 +11,13 @@ use std::collections::HashMap; pub const MAX_PROTOCOL_ID: i64 = (1_i64 << 48) - 1; const ID_ALLOCATION_ATTEMPTS: usize = 16; -pub async fn get_register_id() -> Result { - use std::time::{SystemTime, UNIX_EPOCH}; - - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|d| d.as_secs()) - .unwrap_or(0); - - let ts = timestamp as i64; - if (1..=MAX_PROTOCOL_ID).contains(&ts) { - return Ok(UserId::from(ts)); +pub fn generate_protocol_id() -> UserId { + loop { + let value = rand::random::() & ((1_u64 << 48) - 1); + if value != 0 { + return UserId::from(value as i64); + } } - - // Fall back to random if the timestamp is outside the 48-bit range. - let id = (rand::random::() & ((1_u64 << 48) - 1)) as i64; - Ok(UserId::from(id.max(1))) } pub fn valid_protocol_id(id: i64) -> bool { @@ -42,8 +33,24 @@ pub async fn allocate_registration(iota_id: IotaId, request_id: u32) -> Result<( )); } + let database = pool().await?; + sqlx::query( + "UPDATE registration_leases SET request_id = NULL \ + WHERE request_id IS NOT NULL AND expires_at < UTC_TIMESTAMP()", + ) + .execute(&database) + .await?; + for _ in 0..ID_ALLOCATION_ATTEMPTS { - let id = get_register_id().await?; + let id = generate_protocol_id(); + let user_exists = sqlx::query("SELECT 1 FROM users WHERE id = ? LIMIT 1") + .bind(id.0) + .fetch_optional(&database) + .await? + .is_some(); + if user_exists { + continue; + } let token = uuid::Uuid::new_v4().to_string(); let result = sqlx::query( "INSERT INTO registration_leases (token, user_id, iota_id, request_id, expires_at) \ @@ -53,7 +60,7 @@ pub async fn allocate_registration(iota_id: IotaId, request_id: u32) -> Result<( .bind(id.0) .bind(iota_id.0) .bind(request_id) - .execute(&pool().await?) + .execute(&database) .await; match result { Ok(_) => return Ok((id, token)), @@ -64,7 +71,7 @@ pub async fn allocate_registration(iota_id: IotaId, request_id: u32) -> Result<( ) .bind(iota_id.0) .bind(request_id) - .fetch_optional(&pool().await?) + .fetch_optional(&database) .await?; if let Some(existing) = existing { let current: i8 = existing.get("current"); @@ -172,7 +179,7 @@ fn normalized_ids(ids: &[i64]) -> Vec { ids } -fn append_in_clause(query: &mut QueryBuilder<'_, MySql>, ids: &[i64]) { +fn append_in_clause(query: &mut QueryBuilder, ids: &[i64]) { query.push("("); for (index, id) in ids.iter().enumerate() { if index > 0 { @@ -183,7 +190,7 @@ fn append_in_clause(query: &mut QueryBuilder<'_, MySql>, ids: &[i64]) { query.push(")"); } -async fn fetch_users(mut query: QueryBuilder<'_, MySql>) -> Result> { +async fn fetch_users(mut query: QueryBuilder) -> Result> { query .build_query_as::() .fetch_all(&pool().await?) @@ -336,21 +343,59 @@ pub async fn change_status(id: UserId, value: String) -> Result<()> { } pub async fn change_iota_id(id: UserId, value: Option) -> Result<()> { + let mut transaction = pool().await?.begin().await?; + let previous = sqlx::query("SELECT iota_id FROM users WHERE id = ? FOR UPDATE") + .bind(id.0) + .fetch_optional(&mut *transaction) + .await? + .ok_or(OmegaError::NotFound)?; + let previous_iota_id: Option = previous.get("iota_id"); sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") .bind(value.map(|id| id.0)) .bind(id.0) + .execute(&mut *transaction) + .await?; + if let Some(iota_id) = previous_iota_id { + enqueue_iota_snapshot(&mut transaction, iota_id).await?; + } + if let Some(iota_id) = value { + enqueue_iota_snapshot(&mut transaction, iota_id.0).await?; + } + transaction.commit().await?; + Ok(()) +} + +async fn enqueue_iota_snapshot( + transaction: &mut sqlx::Transaction<'_, MySql>, + iota_id: i64, +) -> Result<()> { + sqlx::query( + "INSERT INTO iota_snapshot_outbox (iota_id, updated_at) VALUES (?, UTC_TIMESTAMP()) \ + ON DUPLICATE KEY UPDATE updated_at = VALUES(updated_at)", + ) + .bind(iota_id) + .execute(&mut **transaction) + .await?; + Ok(()) +} + +pub async fn pending_iota_snapshots() -> Result> { + let rows = sqlx::query("SELECT iota_id FROM iota_snapshot_outbox ORDER BY updated_at") + .fetch_all(&pool().await?) + .await?; + Ok(rows + .into_iter() + .map(|row| IotaId::from(row.get::("iota_id"))) + .collect()) +} + +pub async fn complete_iota_snapshot(iota_id: IotaId) -> Result<()> { + sqlx::query("DELETE FROM iota_snapshot_outbox WHERE iota_id = ?") + .bind(iota_id.0) .execute(&pool().await?) .await?; Ok(()) } -pub async fn change_token(id: UserId, value: String) -> Result<()> { - update( - id, - "UPDATE users SET token = ? WHERE id = ?", - value.into_bytes(), - ) - .await -} /// Delete the central identity while retaining a durable instruction for the /// last hosting Iota. The pending row is intentionally independent of users: /// it must outlive the account row. @@ -488,6 +533,7 @@ pub async fn register_complete_user( } }; result?; + enqueue_iota_snapshot(&mut transaction, iota_id.0).await?; sqlx::query("UPDATE registration_leases SET completed_at = UTC_TIMESTAMP() WHERE token = ?") .bind(®istration_token) .execute(&mut *transaction) @@ -498,19 +544,20 @@ pub async fn register_complete_user( fn valid_username(username: &str) -> bool { !username.is_empty() - && username.chars().count() <= 15 - && !username.chars().any(char::is_control) - && !username.contains(['/', '\\']) + && username.len() <= 15 + && username + .bytes() + .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit()) } #[cfg(test)] mod tests { - use super::{MAX_PROTOCOL_ID, get_register_id, valid_protocol_id}; + use super::{MAX_PROTOCOL_ID, generate_protocol_id, valid_protocol_id}; #[tokio::test] async fn generated_registration_ids_fit_the_mtp_wire_range() { for _ in 0..128 { - assert!(valid_protocol_id(get_register_id().await.unwrap().0)); + assert!(valid_protocol_id(generate_protocol_id().0)); } assert!(!valid_protocol_id(0)); assert!(valid_protocol_id(MAX_PROTOCOL_ID)); diff --git a/src/identity.rs b/src/identity.rs index 0fb416d..c5d373a 100644 --- a/src/identity.rs +++ b/src/identity.rs @@ -1,8 +1,7 @@ use crate::error::{IdentityError, Result}; use mtp::crypto::{Keyring, PublicKeyBundle}; use mtp::files::{ - FileError, load_keyring_raw, load_public_key_bundle, save_keyring_raw, - save_public_key_bundle, + FileError, load_keyring_raw, load_public_key_bundle, save_keyring_raw, save_public_key_bundle, }; use std::{ fs, @@ -70,11 +69,9 @@ impl OmegaIdentity { fn create_at(keyring_path: &Path, public_key_path: &Path) -> Result { let keyring = Keyring::generate(); - save_keyring_raw(&keyring, keyring_path).map_err(|error| { - IdentityError::Storage { - path: keyring_path.to_path_buf(), - source: error, - } + save_keyring_raw(&keyring, keyring_path).map_err(|error| IdentityError::Storage { + path: keyring_path.to_path_buf(), + source: error, })?; Self::persist_public_bundle(&keyring.public_key_bundle(), public_key_path)?; Ok(Self { @@ -232,8 +229,7 @@ mod tests { let directory = test_directory(); let keyring_path = directory.join("omega.mk"); let public_key_path = directory.join("omega.mpkb"); - OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path) - .expect("create identity"); + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path).expect("create identity"); let original_keyring = fs::read(&keyring_path).expect("read keyring"); fs::remove_file(&public_key_path).expect("remove bundle"); @@ -260,8 +256,7 @@ mod tests { let directory = test_directory(); let keyring_path = directory.join("omega.mk"); let public_key_path = directory.join("omega.mpkb"); - OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path) - .expect("create identity"); + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path).expect("create identity"); let other_keyring = Keyring::generate(); save_public_key_bundle(&other_keyring.public_key_bundle(), &public_key_path) .expect("save mismatched bundle"); @@ -293,10 +288,7 @@ mod tests { original_keyring ); assert_eq!( - reloaded - .public_key_bundle() - .try_as_bytes() - .expect("bundle"), + reloaded.public_key_bundle().try_as_bytes().expect("bundle"), first.public_key_bundle().try_as_bytes().expect("bundle") ); diff --git a/src/main.rs b/src/main.rs index d302ff6..e2915c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ pub use error::{OmegaError, Result}; use crate::db::initialize; use crate::state::OmegaState; use crate::transport::omikron_connection; +use crate::transport::omikron_manager; use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; @@ -83,6 +84,7 @@ async fn main() { } } }); + let snapshot_outbox_worker = omikron_manager::spawn_iota_snapshot_outbox_worker(); let port: u16 = env::var("PORT") .ok() .and_then(|s| s.parse().ok()) @@ -100,4 +102,5 @@ async fn main() { } rate_limit_cleanup.abort(); short_link_cleanup.abort(); + snapshot_outbox_worker.abort(); } diff --git a/src/server/api.rs b/src/server/api.rs index 41e5d22..12ac33e 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -13,6 +13,7 @@ use crate::models::UserId; use crate::server::{ middleware, validation::{parse_positive_id, validate_non_empty}, + with_cors, }; use crate::transport::omikron_manager::{ get_all_connections, get_connected_omikron, get_iota_primary_omikron_connection, @@ -193,39 +194,34 @@ pub async fn handle( let method = request.method; let path = request.uri.path().to_string(); if method != Method::OPTIONS && !middleware::allow(request.remote_addr.ip(), &path) { - return response - .status(StatusCode::TOO_MANY_REQUESTS) - .header("access-control-allow-origin", crate::config::cors_origin()) - .body(json(&StatusResponse { + return with_cors(response.status(StatusCode::TOO_MANY_REQUESTS).body(json( + &StatusResponse { status: "error_rate_limited", - })); + }, + ))); } if method == Method::OPTIONS { - return response - .status(StatusCode::OK) - .header("access-control-allow-origin", crate::config::cors_origin()) - .header("access-control-allow-methods", "GET, POST, OPTIONS") - .header("access-control-allow-headers", "*"); + return with_cors(response.status(StatusCode::NO_CONTENT)); } let path_parts: Vec<&str> = path.split('/').filter(|part| !part.is_empty()).collect(); if let ["api", "download", "iota_frontend"] = path_parts.as_slice() { let file_path = format!("{}/downloads/iota_frontend.zip", get_directory()); return match std::fs::read(file_path) { - Ok(bytes) => response - .status(StatusCode::OK) - .header("access-control-allow-origin", crate::config::cors_origin()) - .header("content-type", "application/zip") - .header( - "content-disposition", - "attachment; filename=\"iota_frontend.zip\"", - ) - .body(Bytes::from(bytes)), - Err(_) => response - .status(StatusCode::NOT_FOUND) - .header("access-control-allow-origin", crate::config::cors_origin()) - .body(json(&StatusResponse { + Ok(bytes) => with_cors( + response + .status(StatusCode::OK) + .header("content-type", "application/zip") + .header( + "content-disposition", + "attachment; filename=\"iota_frontend.zip\"", + ) + .body(Bytes::from(bytes)), + ), + Err(_) => with_cors(response.status(StatusCode::NOT_FOUND).body(json( + &StatusResponse { status: "error_not_found", - })), + }, + ))), }; } if let ["direct", short @ ..] = path_parts.as_slice() { @@ -233,19 +229,16 @@ pub async fn handle( let location = crate::server::short_link::get_short_link(&short) .await .unwrap_or_else(|_| "https://tensamin.net".to_string()); - return response - .status(StatusCode::TEMPORARY_REDIRECT) - .header("location", &location); + return with_cors( + response + .status(StatusCode::TEMPORARY_REDIRECT) + .header("location", &location), + ); } let (status, body) = route(&path_parts, &identity) .await .unwrap_or_else(|error| (error.status_code(), error_body(&error))); - response - .status(status) - .header("access-control-allow-origin", crate::config::cors_origin()) - .header("access-control-allow-headers", "*") - .header("access-control-allow-methods", "GET, POST, OPTIONS") - .body(body) + with_cors(response.status(status).body(body)) } pub async fn handle_pattern( diff --git a/src/server/index.rs b/src/server/index.rs index f128c30..0f3aa5e 100644 --- a/src/server/index.rs +++ b/src/server/index.rs @@ -1,6 +1,8 @@ use http::StatusCode; use mtp::webserver::HttpResponse; +use crate::server::with_cors; + pub fn index_handler(response: HttpResponse) -> HttpResponse { let documentation = r#" Omega API Server @@ -33,8 +35,10 @@ live in-memory state; it is empty after an Omega restart until Omikrons sync. All other routes will return this documentation. "#; - response - .status(StatusCode::OK) - .header("content-type", "text/plain; charset=utf-8") - .body(documentation) + with_cors( + response + .status(StatusCode::OK) + .header("content-type", "text/plain; charset=utf-8") + .body(documentation), + ) } diff --git a/src/server/mod.rs b/src/server/mod.rs index 64df9e9..c3acd11 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -4,3 +4,29 @@ pub mod middleware; pub mod short_link; pub mod validation; pub mod web; + +use mtp::webserver::HttpResponse; + +pub(crate) fn with_cors(response: HttpResponse) -> HttpResponse { + response + .header("access-control-allow-origin", crate::config::cors_origin()) + .header("access-control-allow-methods", "GET, POST, OPTIONS") + .header("access-control-allow-headers", "*") +} + +#[cfg(test)] +mod tests { + use super::with_cors; + use mtp::webserver::HttpResponse; + + #[test] + fn web_responses_allow_all_origins() { + let response = with_cors(HttpResponse::default()); + assert_eq!(response.headers["access-control-allow-origin"], "*"); + assert_eq!( + response.headers["access-control-allow-methods"], + "GET, POST, OPTIONS" + ); + assert_eq!(response.headers["access-control-allow-headers"], "*"); + } +} diff --git a/src/server/web.rs b/src/server/web.rs index 5975888..a8219a1 100644 --- a/src/server/web.rs +++ b/src/server/web.rs @@ -1,5 +1,6 @@ use crate::identity::OmegaIdentity; use crate::server::{api, index::index_handler}; +use http::{Method, StatusCode}; use mtp::webserver::{HttpRequest, HttpResponse, RouteParams, WebServerConfig}; use std::{future::Future, pin::Pin, sync::Arc}; @@ -43,5 +44,11 @@ pub fn build_web_config( )? .route_pattern("/api/get/user/{id}", api_pattern_handler(identity.clone()))? .route_pattern("/direct/{short}", api_pattern_handler(identity))? - .fallback(|_request, response| async move { index_handler(response) }) + .fallback(|request, response| async move { + if request.method == Method::OPTIONS { + crate::server::with_cors(response.status(StatusCode::NO_CONTENT)) + } else { + index_handler(response) + } + }) } diff --git a/src/transport/connection.rs b/src/transport/connection.rs index 66d9eb3..92644dc 100644 --- a/src/transport/connection.rs +++ b/src/transport/connection.rs @@ -73,7 +73,6 @@ pub(crate) fn validate_dispatch_fields(value: &CommunicationValue) -> OmikronRes message_type, mtp::codec::CommunicationType::GetUserData | mtp::codec::CommunicationType::ChangeUserData - | mtp::codec::CommunicationType::ChangeIotaData | mtp::codec::CommunicationType::DeleteUser | mtp::codec::CommunicationType::AttachUserBegin | mtp::codec::CommunicationType::AttachUserComplete @@ -147,7 +146,6 @@ mod tests { for message_type in [ CommunicationType::GetUserData, CommunicationType::ChangeUserData, - CommunicationType::ChangeIotaData, CommunicationType::DeleteUser, CommunicationType::AttachUserBegin, CommunicationType::AttachUserComplete, diff --git a/src/transport/handlers/account.rs b/src/transport/handlers/account.rs index ae7215f..9de94a5 100644 --- a/src/transport/handlers/account.rs +++ b/src/transport/handlers/account.rs @@ -84,7 +84,7 @@ pub async fn release_from_iota( match user_repo::change_iota_id(user.id, None).await { Ok(()) => { if let Some(iota) = previous_iota { - crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; + let _ = crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; } connection .send( @@ -225,9 +225,9 @@ pub async fn attach_complete( match user_repo::change_iota_id(user.id, Some(IotaId::from(requester))).await { Ok(()) => { if let Some(iota) = previous_iota.filter(|id| id.0 != requester) { - crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; + let _ = crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; } - crate::transport::omikron_manager::publish_iota_user_snapshot(requester).await; + let _ = crate::transport::omikron_manager::publish_iota_user_snapshot(requester).await; connection .send( &CommunicationValue::new(CommunicationType::Success) @@ -252,7 +252,8 @@ async fn complete_delete( Ok(iota_id) => { let cleanup_pending = iota_id.is_some(); if let Some(iota_id) = iota_id { - crate::transport::omikron_manager::publish_iota_user_snapshot(iota_id.0).await; + let _ = + crate::transport::omikron_manager::publish_iota_user_snapshot(iota_id.0).await; crate::transport::omikron_manager::deliver_pending_erasures(iota_id.0).await; } connection diff --git a/src/transport/handlers/presence.rs b/src/transport/handlers/presence.rs index a958a92..048dadf 100644 --- a/src/transport/handlers/presence.rs +++ b/src/transport/handlers/presence.rs @@ -562,6 +562,15 @@ pub async fn sync_status( &affected_iota_ids.iter().copied().collect::>(), ) .await?; + if sessions.iter().any(|(user_id, _, iota_id)| { + !users + .iter() + .any(|user| user.id.0 == *user_id && user.iota_id == Some(IotaId::from(*iota_id))) + }) { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + } let user_ids = users.iter().map(|user| user.id.0).collect::>(); apply_preferences( &state, diff --git a/src/transport/handlers/register.rs b/src/transport/handlers/register.rs index 2c2501a..c8582e3 100644 --- a/src/transport/handlers/register.rs +++ b/src/transport/handlers/register.rs @@ -135,6 +135,17 @@ pub async fn complete_user( .await { Ok(()) => { + if let Err(error) = + crate::transport::omikron_manager::publish_iota_user_snapshot(iota_id).await + { + crate::log_in!( + crate::util::logger::PrintType::General, + "Could not publish Iota snapshot after registering user {} on Iota {}: {}", + user_id, + iota_id, + error + ); + } connection .send( &CommunicationValue::new(CommunicationType::Success) diff --git a/src/transport/handlers/user_data.rs b/src/transport/handlers/user_data.rs index e6b676a..88dbb57 100644 --- a/src/transport/handlers/user_data.rs +++ b/src/transport/handlers/user_data.rs @@ -264,49 +264,3 @@ pub async fn change_user( ) -> OmikronResult<()> { update_user(connection, value).await } - -pub async fn change_iota( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - let request_id = value.require_id()?; - let reset_data = value.get_data(DataType::ResetToken); - let Some(reset) = reset_data.as_str() else { - return connection - .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) - .await; - }; - let new_token_data = value.get_data(DataType::NewToken); - let Some(new_token) = new_token_data.as_str() else { - return connection - .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) - .await; - }; - let user_id = UserId::from(value.require_sender_i64()?); - let user = match user_repo::get_by_user_id(user_id).await { - Ok(user) => user, - Err(_) => { - return connection - .send_error_response(request_id, CommunicationType::ErrorNotFound) - .await; - } - }; - if user.token != reset { - return connection - .send_error_response(request_id, CommunicationType::ErrorInvalidChallenge) - .await; - } - let result = - match user_repo::change_iota_id(user_id, Some(IotaId::from(value.require_sender_i64()?))) - .await - { - Ok(()) => user_repo::change_token(user_id, new_token.to_owned()).await, - Err(error) => Err(error), - }; - let response = match result { - Ok(()) => CommunicationValue::new(CommunicationType::Success), - Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal) - .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), - }; - connection.send(&response.with_id(request_id)).await -} diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index da4a95e..b4be514 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -449,9 +449,6 @@ impl OmikronConnection { Some(CommunicationType::ChangeUserData) => { crate::transport::handlers::user_data::change_user(self, value).await } - Some(CommunicationType::ChangeIotaData) => { - crate::transport::handlers::user_data::change_iota(self, value).await - } Some(CommunicationType::GetRegister) => { crate::transport::handlers::register::get_register(self, value).await } diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index 9adef33..9c337f5 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -7,6 +7,9 @@ use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; use once_cell::sync::Lazy; use rand::prelude::IteratorRandom; use std::sync::Arc; +use std::time::Duration; +use tokio::task::JoinHandle; +use tokio::time::interval; pub static OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(DashMap::new); @@ -23,6 +26,7 @@ pub async fn add_omikron(conn: Arc) { if let Some(old) = OMIKRON_CONNECTIONS.insert(id, conn.clone()) { old.close().await; } + let _ = flush_iota_snapshot_outbox().await; } pub async fn remove_omikron(omikron_id: i64, connection: &Arc) -> bool { @@ -63,12 +67,18 @@ pub async fn get_all_connections() .await .map_err(|_| ())?; for user in users { - for route in state.presence.routes_for_user(user.id.0) { - if let Some(iotas) = result.get_mut(&route.omikron_id) - && let Some(iota_id) = user.iota_id - && let Some(users) = iotas.get_mut(&iota_id.0) + if let Some(iota_id) = user.iota_id { + for omikron_id in state + .presence + .iota_connections(iota_id.0) + .unwrap_or_default() { - users.push(user.id.0); + if let Some(users) = result + .get_mut(&omikron_id) + .and_then(|iotas| iotas.get_mut(&iota_id.0)) + { + users.push(user.id.0); + } } } } @@ -115,19 +125,18 @@ pub async fn send_to_user(user_id: i64, cv: &CommunicationValue) { } } -/// Publish the authoritative membership list after an attach, migration, or -/// release. Omikron replaces its full local index from this snapshot. -pub async fn publish_iota_user_snapshot(iota_id: i64) { - let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) else { - return; - }; - let Some(connection) = get_connected_omikron(omikron_id) else { - return; - }; - let Ok(users) = user_repo::get_users_by_iota_id(crate::models::IotaId::from(iota_id)).await - else { - return; - }; +/* + * Publish each Iota membership snapshot to every live relay route. Each + * Omikron keeps a local authorization index, so sending only a primary route + * leaves the remaining relays stale after registration or migration. + */ +pub async fn publish_iota_user_snapshot(iota_id: i64) -> OmikronResult<()> { + let state = get_state().ok_or(crate::error::OmegaError::NotConnected)?; + let omikron_ids = state + .presence + .iota_connections(iota_id) + .ok_or(crate::error::OmegaError::NotConnected)?; + let users = user_repo::get_users_by_iota_id(crate::models::IotaId::from(iota_id)).await?; let user_ids = users .into_iter() .map(|user| DataValue::SignedNumber(user.id.0.into())) @@ -135,7 +144,54 @@ pub async fn publish_iota_user_snapshot(iota_id: i64) { let snapshot = CommunicationValue::new(CommunicationType::IotaUserData) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); - let _ = connection.send(&snapshot).await; + for omikron_id in omikron_ids { + let connection = + get_connected_omikron(omikron_id).ok_or(crate::error::OmegaError::NotConnected)?; + connection.send(&snapshot).await?; + } + Ok(()) +} + +pub async fn flush_iota_snapshot_outbox() -> OmikronResult<()> { + for iota_id in user_repo::pending_iota_snapshots().await? { + match publish_iota_user_snapshot(iota_id.0).await { + Ok(()) => { + if let Err(error) = user_repo::complete_iota_snapshot(iota_id).await { + crate::log_in!( + crate::util::logger::PrintType::General, + "Could not complete Iota snapshot outbox entry for {}: {}", + iota_id.0, + error + ); + } + } + Err(error) => { + crate::log_in!( + crate::util::logger::PrintType::General, + "Could not publish Iota snapshot for {}: {}", + iota_id.0, + error + ); + } + } + } + Ok(()) +} + +pub fn spawn_iota_snapshot_outbox_worker() -> JoinHandle<()> { + tokio::spawn(async { + let mut retry = interval(Duration::from_secs(30)); + loop { + retry.tick().await; + if let Err(error) = flush_iota_snapshot_outbox().await { + crate::log_in!( + crate::util::logger::PrintType::General, + "Could not load Iota snapshot outbox: {}", + error + ); + } + } + }) } pub async fn deliver_pending_erasures(iota_id: i64) {