mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-06-13 13:02:13 +00:00
merge into one single crate
This commit is contained in:
parent
053fbeb66e
commit
37551cacce
38 changed files with 79 additions and 2034 deletions
38
rust/Cargo.lock
generated
38
rust/Cargo.lock
generated
|
|
@ -391,12 +391,6 @@ version = "0.9.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "const-oid"
|
|
||||||
version = "0.10.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
|
|
@ -562,7 +556,7 @@ version = "0.7.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const-oid 0.9.6",
|
"const-oid",
|
||||||
"pem-rfc7468",
|
"pem-rfc7468",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
@ -594,7 +588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer 0.10.4",
|
"block-buffer 0.10.4",
|
||||||
"const-oid 0.9.6",
|
"const-oid",
|
||||||
"crypto-common 0.1.7",
|
"crypto-common 0.1.7",
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
@ -606,7 +600,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c"
|
checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer 0.12.0",
|
"block-buffer 0.12.0",
|
||||||
"const-oid 0.10.2",
|
|
||||||
"crypto-common 0.2.1",
|
"crypto-common 0.2.1",
|
||||||
"ctutils",
|
"ctutils",
|
||||||
]
|
]
|
||||||
|
|
@ -1857,25 +1850,6 @@ dependencies = [
|
||||||
"prost",
|
"prost",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "protocols"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"base64",
|
|
||||||
"blahaj",
|
|
||||||
"hmac 0.13.0",
|
|
||||||
"prost",
|
|
||||||
"prost-build",
|
|
||||||
"rand 0.10.1",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sha2 0.11.0",
|
|
||||||
"sqlx",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
"tokio",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.45"
|
version = "1.0.45"
|
||||||
|
|
@ -1991,7 +1965,7 @@ version = "0.9.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
|
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const-oid 0.9.6",
|
"const-oid",
|
||||||
"digest 0.10.7",
|
"digest 0.10.7",
|
||||||
"num-bigint-dig",
|
"num-bigint-dig",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
|
|
@ -2012,20 +1986,24 @@ dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"android-native-keyring-store",
|
"android-native-keyring-store",
|
||||||
"apple-native-keyring-store",
|
"apple-native-keyring-store",
|
||||||
|
"base64",
|
||||||
|
"blahaj",
|
||||||
"chrono",
|
"chrono",
|
||||||
"flutter_rust_bridge",
|
"flutter_rust_bridge",
|
||||||
"hex",
|
"hex",
|
||||||
"hkdf",
|
"hkdf",
|
||||||
|
"hmac 0.13.0",
|
||||||
"keyring-core",
|
"keyring-core",
|
||||||
"libsqlite3-sys",
|
"libsqlite3-sys",
|
||||||
"paste",
|
"paste",
|
||||||
"postcard",
|
"postcard",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
"prost",
|
||||||
"prost-build",
|
"prost-build",
|
||||||
"protocols",
|
|
||||||
"rand 0.10.1",
|
"rand 0.10.1",
|
||||||
"scrypt",
|
"scrypt",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,11 @@ libsqlite3-sys = { version = "0.35.0", features = [
|
||||||
tokio = { version = "1.44", features = ["full"] }
|
tokio = { version = "1.44", features = ["full"] }
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
rand = "0.10.1"
|
rand = "0.10.1"
|
||||||
protocols = { path = "../rust_dependencies/protocols" }
|
prost = "0.14.1"
|
||||||
|
blahaj = "0.6.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
base64 = "0.22.1"
|
||||||
|
hmac = "0.13.0"
|
||||||
hkdf = "0.12.4"
|
hkdf = "0.12.4"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
aes-gcm = "0.10.3"
|
aes-gcm = "0.10.3"
|
||||||
|
|
|
||||||
5
rust/build.rs
Normal file
5
rust/build.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
use std::io::Result;
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
prost_build::compile_protos(&["src/user_discovery/types.proto"], &["src/"])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -52,7 +52,7 @@ impl BackupIdentity {
|
||||||
|
|
||||||
let key_manager: KeyManager = postcard::from_bytes(&decrypted_bytes)?;
|
let key_manager: KeyManager = postcard::from_bytes(&decrypted_bytes)?;
|
||||||
|
|
||||||
key_manager.store_to_keychain(&secure_storage)?;
|
key_manager.store_to_keychain(secure_storage)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ pub(crate) mod log;
|
||||||
mod macros;
|
mod macros;
|
||||||
pub(crate) mod user_discovery;
|
pub(crate) mod user_discovery;
|
||||||
|
|
||||||
|
use crate::user_discovery::traits::{AnnouncedUser, OtherPromotion};
|
||||||
use flutter_rust_bridge::DartFnFuture;
|
use flutter_rust_bridge::DartFnFuture;
|
||||||
use protocols::user_discovery::traits::{AnnouncedUser, OtherPromotion};
|
|
||||||
|
|
||||||
use crate::error::{Result, TwonlyError};
|
use crate::error::{Result, TwonlyError};
|
||||||
use crate::{callback_generator, frb_generated::StreamSink};
|
use crate::{callback_generator, frb_generated::StreamSink};
|
||||||
|
|
@ -50,7 +50,9 @@ pub(crate) fn get_callbacks() -> Result<FlutterCallbacks> {
|
||||||
let caller_opt = CURRENT_CALLBACK_ID.try_with(|&c| c).ok();
|
let caller_opt = CURRENT_CALLBACK_ID.try_with(|&c| c).ok();
|
||||||
|
|
||||||
let lock = FLUTTER_CALLBACKS.read().unwrap();
|
let lock = FLUTTER_CALLBACKS.read().unwrap();
|
||||||
let map = lock.as_ref().ok_or(TwonlyError::MissingCallbackInitialization)?;
|
let map = lock
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(TwonlyError::MissingCallbackInitialization)?;
|
||||||
|
|
||||||
if let Some(id) = caller_opt {
|
if let Some(id) = caller_opt {
|
||||||
if let Some(cb) = map.get(&id) {
|
if let Some(cb) = map.get(&id) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::bridge::callbacks::get_callbacks;
|
use crate::bridge::callbacks::get_callbacks;
|
||||||
use crate::bridge::get_twonly_flutter;
|
use crate::bridge::get_twonly_flutter;
|
||||||
use crate::error::TwonlyError;
|
use crate::error::TwonlyError;
|
||||||
use protocols::user_discovery::error::{Result, UserDiscoveryError};
|
use crate::user_discovery::error::{Result, UserDiscoveryError};
|
||||||
use protocols::user_discovery::traits::UserDiscoveryUtils;
|
use crate::user_discovery::traits::UserDiscoveryUtils;
|
||||||
use protocols::user_discovery::traits::{AnnouncedUser, OtherPromotion, UserDiscoveryStore};
|
use crate::user_discovery::traits::{AnnouncedUser, OtherPromotion, UserDiscoveryStore};
|
||||||
|
#[cfg(test)]
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
|
@ -148,6 +149,7 @@ impl UserDiscoveryStore for UserDiscoveryStoreFlutter {
|
||||||
.ok_or(TwonlyError::DartError.into())
|
.ok_or(TwonlyError::DartError.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
async fn get_all_announced_users(
|
async fn get_all_announced_users(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<HashMap<AnnouncedUser, Vec<(i64, Option<i64>)>>> {
|
) -> Result<HashMap<AnnouncedUser, Vec<(i64, Option<i64>)>>> {
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,12 @@ use crate::error::Result;
|
||||||
use crate::error::TwonlyError;
|
use crate::error::TwonlyError;
|
||||||
use crate::keys::KeyManager;
|
use crate::keys::KeyManager;
|
||||||
use crate::secure_storage::SecureStorage;
|
use crate::secure_storage::SecureStorage;
|
||||||
|
use crate::user_discovery::UserDiscovery;
|
||||||
use crate::utils::Shared;
|
use crate::utils::Shared;
|
||||||
use flutter_rust_bridge::frb;
|
use flutter_rust_bridge::frb;
|
||||||
use protocols::user_discovery::UserDiscovery;
|
|
||||||
|
|
||||||
pub use protocols::user_discovery::traits::AnnouncedUser;
|
pub use crate::user_discovery::traits::AnnouncedUser;
|
||||||
pub use protocols::user_discovery::traits::OtherPromotion;
|
pub use crate::user_discovery::traits::OtherPromotion;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
pub struct InitConfig {
|
pub struct InitConfig {
|
||||||
|
|
@ -57,9 +57,9 @@ pub(crate) struct TwonlyFlutter {
|
||||||
pub(super) fn get_twonly_flutter() -> Result<&'static TwonlyFlutter> {
|
pub(super) fn get_twonly_flutter() -> Result<&'static TwonlyFlutter> {
|
||||||
let ctx = Context::get_static()?;
|
let ctx = Context::get_static()?;
|
||||||
if let Context::Flutter(twonly) = ctx {
|
if let Context::Flutter(twonly) = ctx {
|
||||||
return Ok(twonly);
|
Ok(twonly)
|
||||||
} else {
|
} else {
|
||||||
return Err(TwonlyError::Initialization);
|
Err(TwonlyError::Initialization)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ impl RustBackupIdentity {
|
||||||
|
|
||||||
pub async fn get_identity_backup_bytes() -> Result<Vec<u8>> {
|
pub async fn get_identity_backup_bytes() -> Result<Vec<u8>> {
|
||||||
let key_manager = get_twonly_flutter()?.key_manager.lock().await;
|
let key_manager = get_twonly_flutter()?.key_manager.lock().await;
|
||||||
return BackupIdentity::encrypt_key_manager(&key_manager);
|
BackupIdentity::encrypt_key_manager(&key_manager)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn restore_identity_backup(
|
pub async fn restore_identity_backup(
|
||||||
|
|
@ -70,7 +70,7 @@ impl RustBackupIdentity {
|
||||||
impl RustBackupArchive {
|
impl RustBackupArchive {
|
||||||
pub async fn create_backup_archive() -> Result<(String, String)> {
|
pub async fn create_backup_archive() -> Result<(String, String)> {
|
||||||
let ctx = Context::get_static()?;
|
let ctx = Context::get_static()?;
|
||||||
let path = BackupArchive::create_backup(&ctx).await?;
|
let path = BackupArchive::create_backup(ctx).await?;
|
||||||
let key_manager = get_twonly_flutter()?.key_manager.lock().await;
|
let key_manager = get_twonly_flutter()?.key_manager.lock().await;
|
||||||
let token = hex::encode(key_manager.main_key.get_backup_download_token());
|
let token = hex::encode(key_manager.main_key.get_backup_download_token());
|
||||||
Ok((token, path.canonicalize()?.to_string_lossy().to_string()))
|
Ok((token, path.canonicalize()?.to_string_lossy().to_string()))
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::user_discovery::UserDiscovery;
|
||||||
use crate::{
|
use crate::{
|
||||||
bridge::{
|
bridge::{
|
||||||
callbacks::user_discovery::{UserDiscoveryStoreFlutter, UserDiscoveryUtilsFlutter},
|
callbacks::user_discovery::{UserDiscoveryStoreFlutter, UserDiscoveryUtilsFlutter},
|
||||||
|
|
@ -9,7 +10,6 @@ use crate::{
|
||||||
log::init_tracing,
|
log::init_tracing,
|
||||||
utils::Shared,
|
utils::Shared,
|
||||||
};
|
};
|
||||||
use protocols::user_discovery::UserDiscovery;
|
|
||||||
use std::{path::PathBuf, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
use tokio::sync::{Mutex, OnceCell};
|
use tokio::sync::{Mutex, OnceCell};
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
|
||||||
|
|
@ -88,11 +88,10 @@ macro_rules! generate_test_select {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn [<test_ $select_fn>]() {
|
async fn [<test_ $select_fn>]() {
|
||||||
use crate::database::Database;
|
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let db_path = dir.path().join("test.sqlite").display().to_string();
|
let db_path = dir.path().join("test.sqlite").display().to_string();
|
||||||
let db = Database::new(&db_path, None, false).await.unwrap();
|
let db = crate::database::Database::new(&db_path, None, false).await.unwrap();
|
||||||
db.run_migrations().await.unwrap();
|
db.run_migrations().await.unwrap();
|
||||||
|
|
||||||
$struct::$insert_fn(&db.pool, $($arg),+).await.unwrap();
|
$struct::$insert_fn(&db.pool, $($arg),+).await.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
use crate::user_discovery::error::UserDiscoveryError;
|
||||||
use hex::FromHexError;
|
use hex::FromHexError;
|
||||||
use protocols::user_discovery::error::UserDiscoveryError;
|
|
||||||
use scrypt::errors::{InvalidOutputLen, InvalidParams};
|
use scrypt::errors::{InvalidOutputLen, InvalidParams};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use zip::result::ZipError;
|
use zip::result::ZipError;
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ impl MainKey {
|
||||||
self.decrypt_with_info(b"backup_key", encrypted_backup)
|
self.decrypt_with_info(b"backup_key", encrypted_backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encrypts a newly generated media key using the derived Media Main Key.
|
// Encrypts a newly generated media key using the derived Media Main Key.
|
||||||
// pub fn encrypt_media_key(&self, media_key: &[u8; 32]) -> Vec<u8> {
|
// pub fn encrypt_media_key(&self, media_key: &[u8; 32]) -> Vec<u8> {
|
||||||
// self.encrypt_with_info(b"media_main_key", media_key)
|
// self.encrypt_with_info(b"media_main_key", media_key)
|
||||||
// }
|
// }
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ mod error;
|
||||||
mod frb_generated;
|
mod frb_generated;
|
||||||
mod keys;
|
mod keys;
|
||||||
mod log;
|
mod log;
|
||||||
|
mod passwordless_recovery;
|
||||||
mod secure_storage;
|
mod secure_storage;
|
||||||
mod standalone;
|
mod standalone;
|
||||||
|
mod user_discovery;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ pub mod stores;
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
#[cfg(test)]
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::collections::{HashSet};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::u8;
|
|
||||||
use blahaj::{Share, Sharks};
|
use blahaj::{Share, Sharks};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -184,6 +185,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
/// * `Ok(HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>)` - All connections the user has discovered
|
/// * `Ok(HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>)` - All connections the user has discovered
|
||||||
/// * `Err(UserDiscoveryError)` - If there where erros in the store.
|
/// * `Err(UserDiscoveryError)` - If there where erros in the store.
|
||||||
///
|
///
|
||||||
|
#[cfg(test)]
|
||||||
pub async fn get_all_announced_users(
|
pub async fn get_all_announced_users(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>> {
|
) -> Result<HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>> {
|
||||||
|
|
@ -384,7 +386,8 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
config.promotion_version += 1;
|
config.promotion_version += 1;
|
||||||
new_promotion_version = config.promotion_version;
|
new_promotion_version = config.promotion_version;
|
||||||
announcement_version = config.announcement_version;
|
announcement_version = config.announcement_version;
|
||||||
}).await?;
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
let message = UserDiscoveryMessage {
|
let message = UserDiscoveryMessage {
|
||||||
version: Some(UserDiscoveryVersion {
|
version: Some(UserDiscoveryVersion {
|
||||||
|
|
@ -430,7 +433,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
}
|
}
|
||||||
.encode_to_vec();
|
.encode_to_vec();
|
||||||
|
|
||||||
let sharks = Sharks(config.threshold as u8);
|
let sharks = Sharks(config.threshold);
|
||||||
let dealer = sharks.dealer(&encrypted_announcement);
|
let dealer = sharks.dealer(&encrypted_announcement);
|
||||||
|
|
||||||
let mut shares: Vec<Vec<u8>> = dealer
|
let mut shares: Vec<Vec<u8>> = dealer
|
||||||
|
|
@ -476,10 +479,8 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut UserDiscoveryConfig),
|
F: FnOnce(&mut UserDiscoveryConfig),
|
||||||
{
|
{
|
||||||
let _lock = tokio::time::timeout(
|
let _lock =
|
||||||
std::time::Duration::from_secs(10),
|
tokio::time::timeout(std::time::Duration::from_secs(10), self.config_lock.lock())
|
||||||
self.config_lock.lock(),
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
let mut config: UserDiscoveryConfig =
|
let mut config: UserDiscoveryConfig =
|
||||||
|
|
@ -539,9 +540,9 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
.verify_stored_pubkey(contact_id, &signed_data.public_key)
|
.verify_stored_pubkey(contact_id, &signed_data.public_key)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
return Err(UserDiscoveryError::MaliciousAnnouncementData(format!(
|
return Err(UserDiscoveryError::MaliciousAnnouncementData(
|
||||||
"public key does not match with stored one",
|
"public key does not match with stored one".to_string(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self
|
if !self
|
||||||
|
|
@ -553,9 +554,9 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
return Err(UserDiscoveryError::MaliciousAnnouncementData(format!(
|
return Err(UserDiscoveryError::MaliciousAnnouncementData(
|
||||||
"signature invalid",
|
"signature invalid".to_string(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only add this user to the promotions if the users enabled this feature
|
// Only add this user to the promotions if the users enabled this feature
|
||||||
|
|
@ -567,7 +568,8 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
config.promotion_version += 1;
|
config.promotion_version += 1;
|
||||||
new_promotion_version = config.promotion_version;
|
new_promotion_version = config.promotion_version;
|
||||||
announcement_version = config.announcement_version;
|
announcement_version = config.announcement_version;
|
||||||
}).await?;
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
let message = UserDiscoveryMessage {
|
let message = UserDiscoveryMessage {
|
||||||
version: Some(UserDiscoveryVersion {
|
version: Some(UserDiscoveryVersion {
|
||||||
|
|
@ -624,11 +626,10 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
self.store
|
self.store
|
||||||
.push_new_user_relation(
|
.push_new_user_relation(
|
||||||
promotion.from_contact_id,
|
promotion.from_contact_id,
|
||||||
announced_user,
|
announced_user.clone(),
|
||||||
promotion.public_key_verified_timestamp,
|
promotion.public_key_verified_timestamp,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -731,9 +732,9 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
return Err(UserDiscoveryError::MaliciousAnnouncementData(format!(
|
return Err(UserDiscoveryError::MaliciousAnnouncementData(
|
||||||
"signature is invalid",
|
"signature is invalid".to_string(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!("Announcement valid.");
|
tracing::debug!("Announcement valid.");
|
||||||
|
|
@ -790,4 +791,3 @@ impl Default for UserDiscoveryConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ impl UserDiscoveryStore for InMemoryStore {
|
||||||
async fn get_own_promotions_after_version(&self, version: u32) -> Result<Vec<Vec<u8>>> {
|
async fn get_own_promotions_after_version(&self, version: u32) -> Result<Vec<Vec<u8>>> {
|
||||||
let storage = self.storage();
|
let storage = self.storage();
|
||||||
let elements = storage.own_promotions[(version as usize)..]
|
let elements = storage.own_promotions[(version as usize)..]
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|(_, promotion)| promotion.to_owned())
|
.map(|(_, promotion)| promotion.to_owned())
|
||||||
.collect();
|
.collect();
|
||||||
Ok(elements)
|
Ok(elements)
|
||||||
|
|
@ -107,7 +107,7 @@ impl UserDiscoveryStore for InMemoryStore {
|
||||||
if let Some(element) = element {
|
if let Some(element) = element {
|
||||||
return Ok(Some(element.1.to_owned()));
|
return Ok(Some(element.1.to_owned()));
|
||||||
}
|
}
|
||||||
return Ok(None);
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn store_other_promotion(&self, promotion: OtherPromotion) -> Result<()> {
|
async fn store_other_promotion(&self, promotion: OtherPromotion) -> Result<()> {
|
||||||
|
|
@ -158,7 +158,7 @@ impl UserDiscoveryStore for InMemoryStore {
|
||||||
let entry = storage
|
let entry = storage
|
||||||
.announced_users
|
.announced_users
|
||||||
.entry(announced_user.clone())
|
.entry(announced_user.clone())
|
||||||
.or_insert(vec![]);
|
.or_default();
|
||||||
if announced_user.user_id != from_contact_id {
|
if announced_user.user_id != from_contact_id {
|
||||||
if let Some(found) = entry.iter_mut().find(|x| x.0 == from_contact_id) {
|
if let Some(found) = entry.iter_mut().find(|x| x.0 == from_contact_id) {
|
||||||
found.1 = public_key_verified_timestamp;
|
found.1 = public_key_verified_timestamp;
|
||||||
4
rust/src/user_discovery/stores/mod.rs
Normal file
4
rust/src/user_discovery/stores/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#[cfg(test)]
|
||||||
|
mod in_memory_store;
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(super) use in_memory_store::InMemoryStore;
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
|
#[cfg(test)]
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::user_discovery::error::Result;
|
use crate::user_discovery::error::Result;
|
||||||
use crate::user_discovery::UserID;
|
use crate::user_discovery::UserID;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
|
/// Type alias used in `UserDiscoveryStore::get_all_announced_users`.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub type AnnouncedUserMap = HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>;
|
||||||
|
|
||||||
#[derive(Clone, sqlx::FromRow)]
|
#[derive(Clone, sqlx::FromRow)]
|
||||||
pub struct OtherPromotion {
|
pub struct OtherPromotion {
|
||||||
pub promotion_id: u32,
|
pub promotion_id: u32,
|
||||||
|
|
@ -65,9 +70,8 @@ pub trait UserDiscoveryStore {
|
||||||
public_key_verified_timestamp: Option<i64>,
|
public_key_verified_timestamp: Option<i64>,
|
||||||
) -> impl Future<Output = Result<()>> + Send;
|
) -> impl Future<Output = Result<()>> + Send;
|
||||||
|
|
||||||
fn get_all_announced_users(
|
#[cfg(test)]
|
||||||
&self,
|
fn get_all_announced_users(&self) -> impl Future<Output = Result<AnnouncedUserMap>> + Send;
|
||||||
) -> impl Future<Output = Result<HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>>> + Send;
|
|
||||||
|
|
||||||
fn get_contact_promotion(
|
fn get_contact_promotion(
|
||||||
&self,
|
&self,
|
||||||
0
rust_dependencies/.gitignore
vendored
0
rust_dependencies/.gitignore
vendored
1639
rust_dependencies/Cargo.lock
generated
1639
rust_dependencies/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +0,0 @@
|
||||||
[workspace]
|
|
||||||
members = ["protocols"]
|
|
||||||
resolver = "3"
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "protocols"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["rlib", "cdylib", "staticlib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
thiserror = "2.0.18"
|
|
||||||
tracing = "0.1.44"
|
|
||||||
serde = "1.0.228"
|
|
||||||
prost = "0.14.1"
|
|
||||||
rand = "0.10.1"
|
|
||||||
blahaj = "0.6.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
base64 = "0.22.1"
|
|
||||||
hmac = "0.13.0"
|
|
||||||
sha2 = "0.11.0"
|
|
||||||
tokio = { version = "1.44", features = ["full"] }
|
|
||||||
sqlx = { version = "0.9.0-alpha.1", default-features = false, features = [
|
|
||||||
"derive",
|
|
||||||
] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
pretty_env_logger = "0.5.0"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
prost-build = "0.14.1"
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
use std::io::Result;
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
prost_build::compile_protos(
|
|
||||||
&[
|
|
||||||
"src/user_discovery/types.proto",
|
|
||||||
"src/key_verification/types.proto",
|
|
||||||
],
|
|
||||||
&["src/"],
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
use prost::DecodeError;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
pub type Result<T> = core::result::Result<T, KeyVerificationError>;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum KeyVerificationError {
|
|
||||||
#[error("The prefix deeplink url must start with https:// and end with a #")]
|
|
||||||
InvalidDeeplinkPrefix,
|
|
||||||
|
|
||||||
#[error("Invalid qr text")]
|
|
||||||
InvalidQrText,
|
|
||||||
|
|
||||||
#[error(
|
|
||||||
"Contact user_id is known and the stored public_key does not match the received user id"
|
|
||||||
)]
|
|
||||||
InvalidPublicKeyAndUserIdCombination,
|
|
||||||
|
|
||||||
#[error("Store error: `{0}`")]
|
|
||||||
Store(String),
|
|
||||||
|
|
||||||
#[error("`{0}`")]
|
|
||||||
Base64(#[from] base64::DecodeError),
|
|
||||||
|
|
||||||
#[error("`{0}`")]
|
|
||||||
Prost(#[from] DecodeError),
|
|
||||||
|
|
||||||
#[error("`{0}`")]
|
|
||||||
Hmac(#[from] hmac::digest::InvalidLength),
|
|
||||||
}
|
|
||||||
|
|
@ -1,194 +0,0 @@
|
||||||
use crate::key_verification::{error::KeyVerificationError, traits::KeyVerificationStore};
|
|
||||||
use crate::user_discovery::UserID;
|
|
||||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
|
|
||||||
use error::Result;
|
|
||||||
use hmac::{Hmac, KeyInit, Mac};
|
|
||||||
use prost::Message;
|
|
||||||
use sha2::Sha256;
|
|
||||||
|
|
||||||
pub(crate) mod error;
|
|
||||||
pub mod stores;
|
|
||||||
pub mod traits;
|
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/key_verification.rs"));
|
|
||||||
|
|
||||||
pub struct KeyVerificationConfig {
|
|
||||||
/// The link prefix for the qr code which should be registered as a deeplink on Android and a universal link on iOS
|
|
||||||
/// The link MUST start with a https:// and end with a #
|
|
||||||
/// The link should contain the username of the user so the application can show the scanned user without internet
|
|
||||||
/// Example: https://me.twonly.eu/tobi#
|
|
||||||
deeplink_prefix: String,
|
|
||||||
/// The user ID used to calculate the verification proof
|
|
||||||
user_id: UserID,
|
|
||||||
/// The public_key of the user to calculate the verification proof
|
|
||||||
public_key: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ScannedUser {
|
|
||||||
pub user_id: UserID,
|
|
||||||
pub public_key: Vec<u8>,
|
|
||||||
pub verification_proof: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct KeyVerification<Store: KeyVerificationStore> {
|
|
||||||
store: Store,
|
|
||||||
config: KeyVerificationConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Store: KeyVerificationStore> KeyVerification<Store> {
|
|
||||||
pub fn new(store: Store, config: KeyVerificationConfig) -> Result<KeyVerification<Store>> {
|
|
||||||
if !config.deeplink_prefix.starts_with("https://") || !config.deeplink_prefix.ends_with("#")
|
|
||||||
{
|
|
||||||
return Err(KeyVerificationError::InvalidDeeplinkPrefix);
|
|
||||||
}
|
|
||||||
Ok(Self { store, config })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates the a string which should be displayed in the UI so others can scan it.
|
|
||||||
pub fn generate_qr_text(&self) -> Result<String> {
|
|
||||||
// 10 Bytes should be enough. Tokens are only valid for one day and then deleted.
|
|
||||||
let secret_verification_token: Vec<u8> = rand::random_iter().take(16).collect();
|
|
||||||
|
|
||||||
self.store
|
|
||||||
.push_new_secret_verification_token(&secret_verification_token)?;
|
|
||||||
|
|
||||||
let verification_data = VerificationData {
|
|
||||||
user_id: self.config.user_id,
|
|
||||||
public_key: self.config.public_key.clone(),
|
|
||||||
secret_verification_token,
|
|
||||||
};
|
|
||||||
|
|
||||||
let verification_data_bytes = verification_data.encode_to_vec();
|
|
||||||
let encoded = URL_SAFE_NO_PAD.encode(verification_data_bytes);
|
|
||||||
|
|
||||||
Ok(format!("{}{}", self.config.deeplink_prefix, encoded))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles the scanned qr code text and creates a response message
|
|
||||||
/// which can be send to the other person
|
|
||||||
pub fn get_user_from_scanned_qr_text(&self, received_text: &str) -> Result<ScannedUser> {
|
|
||||||
let splitted: Vec<_> = received_text.split('#').collect();
|
|
||||||
if splitted.len() != 2 {
|
|
||||||
tracing::info!("Scanned qr text does not contain a #");
|
|
||||||
return Err(KeyVerificationError::InvalidQrText);
|
|
||||||
}
|
|
||||||
let verification_data_bytes = URL_SAFE_NO_PAD.decode(splitted[1])?;
|
|
||||||
let verification_data = VerificationData::decode(verification_data_bytes.as_slice())?;
|
|
||||||
|
|
||||||
let mut mac = Hmac::<Sha256>::new_from_slice(&verification_data.secret_verification_token)?;
|
|
||||||
mac.update(&self.config.user_id.to_le_bytes());
|
|
||||||
mac.update(&self.config.public_key);
|
|
||||||
mac.update(&verification_data.user_id.to_le_bytes());
|
|
||||||
mac.update(&verification_data.public_key);
|
|
||||||
|
|
||||||
let verification_proof = mac.finalize().into_bytes().to_vec();
|
|
||||||
|
|
||||||
Ok(ScannedUser {
|
|
||||||
user_id: verification_data.user_id,
|
|
||||||
public_key: verification_data.public_key,
|
|
||||||
verification_proof,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks whether the received verification proof is valid
|
|
||||||
pub fn is_received_verification_proof_valid(
|
|
||||||
&self,
|
|
||||||
from_user_id: UserID,
|
|
||||||
public_key: Vec<u8>,
|
|
||||||
verification_proof: Vec<u8>,
|
|
||||||
) -> Result<bool> {
|
|
||||||
let verification_tokens = self.store.get_all_valid_verification_tokens()?;
|
|
||||||
|
|
||||||
for verification_token in &verification_tokens {
|
|
||||||
let calculated_verification_proof = {
|
|
||||||
let mut mac = Hmac::<Sha256>::new_from_slice(verification_token)?;
|
|
||||||
mac.update(&from_user_id.to_le_bytes());
|
|
||||||
mac.update(&public_key);
|
|
||||||
mac.update(&self.config.user_id.to_le_bytes());
|
|
||||||
mac.update(&self.config.public_key);
|
|
||||||
mac.finalize().into_bytes().to_vec()
|
|
||||||
};
|
|
||||||
|
|
||||||
if calculated_verification_proof == verification_proof {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::key_verification::{stores::InMemoryStore, KeyVerification, KeyVerificationConfig};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_key_verification() {
|
|
||||||
let _ = pretty_env_logger::try_init();
|
|
||||||
|
|
||||||
const ALICE_ID: i64 = 10;
|
|
||||||
const BOB_ID: i64 = 11;
|
|
||||||
|
|
||||||
let alice_kv = KeyVerification::new(
|
|
||||||
InMemoryStore::default(),
|
|
||||||
KeyVerificationConfig {
|
|
||||||
user_id: ALICE_ID,
|
|
||||||
public_key: vec![ALICE_ID as u8; 32],
|
|
||||||
deeplink_prefix: "https://me.twonly.eu/alice#".into(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let bob_kv = KeyVerification::new(
|
|
||||||
InMemoryStore::default(),
|
|
||||||
KeyVerificationConfig {
|
|
||||||
user_id: BOB_ID,
|
|
||||||
public_key: vec![BOB_ID as u8; 32],
|
|
||||||
deeplink_prefix: "https://me.twonly.eu/bob#".into(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let qr_code_text = alice_kv.generate_qr_text().unwrap();
|
|
||||||
assert_eq!(qr_code_text.len(), 99);
|
|
||||||
|
|
||||||
tracing::debug!("Generated QR-Code-Link: {qr_code_text}");
|
|
||||||
|
|
||||||
let scanned_user = bob_kv.get_user_from_scanned_qr_text(&qr_code_text).unwrap();
|
|
||||||
|
|
||||||
// THIS must be done by the application
|
|
||||||
assert_eq!(scanned_user.user_id, ALICE_ID);
|
|
||||||
assert_eq!(scanned_user.public_key, vec![ALICE_ID as u8; 32]);
|
|
||||||
|
|
||||||
// SEND scanned_user.verification_proof over the establish e2ee protected session if public_key verification was valid.
|
|
||||||
|
|
||||||
let valid_verification_proof = alice_kv
|
|
||||||
.is_received_verification_proof_valid(
|
|
||||||
BOB_ID,
|
|
||||||
vec![BOB_ID as u8; 32],
|
|
||||||
scanned_user.verification_proof.clone(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(valid_verification_proof, true);
|
|
||||||
|
|
||||||
let valid_verification_proof = alice_kv
|
|
||||||
.is_received_verification_proof_valid(
|
|
||||||
BOB_ID,
|
|
||||||
vec![(BOB_ID + 1) as u8; 32],
|
|
||||||
scanned_user.verification_proof.clone(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(valid_verification_proof, false);
|
|
||||||
|
|
||||||
let mut modified_proof = scanned_user.verification_proof;
|
|
||||||
modified_proof[0] = modified_proof[0] + 1;
|
|
||||||
|
|
||||||
let valid_verification_proof = alice_kv
|
|
||||||
.is_received_verification_proof_valid(BOB_ID, vec![BOB_ID as u8; 32], modified_proof)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(valid_verification_proof, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use crate::key_verification::{error::Result, traits::KeyVerificationStore};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct InMemoryStore {
|
|
||||||
verification_tokens: Arc<Mutex<Vec<Vec<u8>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyVerificationStore for InMemoryStore {
|
|
||||||
fn push_new_secret_verification_token(&self, token: &[u8]) -> Result<()> {
|
|
||||||
self.verification_tokens
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.push(token.to_vec());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_all_valid_verification_tokens(&self) -> Result<Vec<Vec<u8>>> {
|
|
||||||
Ok(self.verification_tokens.lock().unwrap().clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
mod in_memory_store;
|
|
||||||
|
|
||||||
pub use in_memory_store::InMemoryStore;
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
use super::error::Result;
|
|
||||||
pub trait KeyVerificationStore {
|
|
||||||
fn push_new_secret_verification_token(&self, token: &[u8]) -> Result<()>;
|
|
||||||
/// This function should return all tokens from the last 24h
|
|
||||||
/// All other tokens can be removed from the database
|
|
||||||
fn get_all_valid_verification_tokens(&self) -> Result<Vec<Vec<u8>>>;
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package key_verification;
|
|
||||||
|
|
||||||
message VerificationData {
|
|
||||||
int64 user_id = 1;
|
|
||||||
bytes public_key = 2;
|
|
||||||
bytes secret_verification_token = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message VerificationMessage {
|
|
||||||
bytes calculated_mac = 1;
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
pub mod key_verification;
|
|
||||||
pub mod passwordless_recovery;
|
|
||||||
pub mod user_discovery;
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
mod in_memory_store;
|
|
||||||
pub use in_memory_store::InMemoryStore;
|
|
||||||
|
|
@ -18,7 +18,7 @@ protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "qr.proto"
|
||||||
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "data.proto"
|
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "data.proto"
|
||||||
|
|
||||||
mkdir "$GENERATED_DIR/user_discovery/" &>/dev/null
|
mkdir "$GENERATED_DIR/user_discovery/" &>/dev/null
|
||||||
protoc --proto_path="./rust_dependencies/protocols/src/user_discovery/" --dart_out="$GENERATED_DIR/user_discovery/" "types.proto"
|
protoc --proto_path="./rust/src/user_discovery/" --dart_out="$GENERATED_DIR/user_discovery/" "types.proto"
|
||||||
|
|
||||||
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "push_notification.proto"
|
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "push_notification.proto"
|
||||||
protoc --proto_path="$CLIENT_DIR" --swift_out="./ios/NotificationService/" "push_notification.proto"
|
protoc --proto_path="$CLIENT_DIR" --swift_out="./ios/NotificationService/" "push_notification.proto"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue