feat: encrypt collab update (#3215)

* feat: implement encrypt and decrypt

* feat: encrypt and decrypt

* feat: update user profile with encrypt

* chore: store encryption sign

* fix: login in setting menu

* chore: show encryption account name

* chore: fix test

* ci: fix warnings

* test: enable supabase test

* chore: fix test and rename column

* fix: update user profile after set the secret

* fix: encryption with wrong secret

* fix: don't save user data if the return value of did_sign_up is err

* chore: encrypt snapshot data

* chore: refactor snapshots interface

* ci: add tests

* chore: update collab rev
This commit is contained in:
Nathan.fooo
2023-08-17 23:46:39 +08:00
committed by GitHub
parent 103f56922f
commit 649b0a135a
103 changed files with 2825 additions and 905 deletions

View File

@ -5,18 +5,19 @@ use flowy_user_deps::entities::SignUpResponse;
use lib_infra::box_any::BoxAny;
use crate::supabase_test::util::{
collab_service, database_service, get_supabase_config, sign_up_param, user_auth_service,
collab_service, database_service, get_supabase_ci_config, third_party_sign_up_param,
user_auth_service,
};
#[tokio::test]
async fn supabase_create_workspace_test() {
if get_supabase_config().is_none() {
if get_supabase_ci_config().is_none() {
return;
}
let user_service = user_auth_service();
let uuid = Uuid::new_v4().to_string();
let params = sign_up_param(uuid);
let params = third_party_sign_up_param(uuid);
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
let collab_service = collab_service();

View File

@ -1,8 +1,6 @@
use assert_json_diff::assert_json_eq;
use collab_plugins::cloud_storage::{CollabObject, CollabType};
use futures::future::join_all;
use serde_json::json;
use tokio::task;
use uuid::Uuid;
use yrs::types::ToJson;
use yrs::updates::decoder::Decode;
@ -12,12 +10,13 @@ use flowy_user_deps::entities::SignUpResponse;
use lib_infra::box_any::BoxAny;
use crate::supabase_test::util::{
collab_service, folder_service, get_supabase_config, sign_up_param, user_auth_service,
collab_service, folder_service, get_supabase_ci_config, third_party_sign_up_param,
user_auth_service,
};
#[tokio::test]
async fn supabase_create_workspace_test() {
if get_supabase_config().is_none() {
if get_supabase_ci_config().is_none() {
return;
}
@ -29,7 +28,7 @@ async fn supabase_create_workspace_test() {
#[tokio::test]
async fn supabase_get_folder_test() {
if get_supabase_config().is_none() {
if get_supabase_ci_config().is_none() {
return;
}
@ -37,7 +36,7 @@ async fn supabase_get_folder_test() {
let user_service = user_auth_service();
let collab_service = collab_service();
let uuid = Uuid::new_v4().to_string();
let params = sign_up_param(uuid);
let params = third_party_sign_up_param(uuid);
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
let collab_object = CollabObject {
@ -75,26 +74,17 @@ async fn supabase_get_folder_test() {
.unwrap();
assert_eq!(updates.len(), 2);
// The init sync will try to merge the updates into one. Spawn 5 tasks to simulate
// multiple clients trying to init sync at the same time.
let mut handles = Vec::new();
for _ in 0..5 {
let cloned_collab_service = collab_service.clone();
let cloned_collab_object = collab_object.clone();
let handle = task::spawn(async move {
cloned_collab_service
.send_init_sync(&cloned_collab_object, 3, vec![])
.await
.unwrap();
});
handles.push(handle);
collab_service
.send_init_sync(&collab_object, 3, vec![])
.await
.unwrap();
}
let _results: Vec<_> = join_all(handles).await;
// after the init sync, the updates should be merged into one.
let updates: Vec<Vec<u8>> = folder_service
.get_folder_updates(&user.latest_workspace.id, user.user_id)
.await
.unwrap();
assert_eq!(updates.len(), 1);
// Other the init sync, try to get the updates from the server.
let remote_update = updates.first().unwrap().clone();
@ -112,7 +102,7 @@ async fn supabase_get_folder_test() {
/// Finally, it asserts that the duplicated updates don't affect the overall data consistency in Supabase.
#[tokio::test]
async fn supabase_duplicate_updates_test() {
if get_supabase_config().is_none() {
if get_supabase_ci_config().is_none() {
return;
}
@ -120,7 +110,7 @@ async fn supabase_duplicate_updates_test() {
let user_service = user_auth_service();
let collab_service = collab_service();
let uuid = Uuid::new_v4().to_string();
let params = sign_up_param(uuid);
let params = third_party_sign_up_param(uuid);
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
let collab_object = CollabObject {
@ -206,9 +196,20 @@ async fn supabase_duplicate_updates_test() {
}
}
/// The state vector of doc;
/// ```json
/// "map": {},
/// "array": []
/// ```
/// The old version of doc:
/// ```json
/// "map": {}
/// ```
///
/// Try to apply the updates from doc to old version doc and check the result.
#[tokio::test]
async fn supabase_diff_state_vec_test() {
if get_supabase_config().is_none() {
async fn supabase_diff_state_vector_test() {
if get_supabase_ci_config().is_none() {
return;
}
@ -216,7 +217,7 @@ async fn supabase_diff_state_vec_test() {
let user_service = user_auth_service();
let collab_service = collab_service();
let uuid = Uuid::new_v4().to_string();
let params = sign_up_param(uuid);
let params = third_party_sign_up_param(uuid);
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
let collab_object = CollabObject {
@ -278,3 +279,22 @@ async fn supabase_diff_state_vec_test() {
})
);
}
// #[tokio::test]
// async fn print_folder_object_test() {
// if get_supabase_dev_config().is_none() {
// return;
// }
// let secret = Some("43bSxEPHeNkk5ZxxEYOfAjjd7sK2DJ$vVnxwuNc5ru0iKFvhs8wLg==".to_string());
// print_encryption_folder("f8b14b84-e8ec-4cf4-a318-c1e008ecfdfa", secret).await;
// }
//
// #[tokio::test]
// async fn print_folder_snapshot_object_test() {
// if get_supabase_dev_config().is_none() {
// return;
// }
// let secret = Some("NTXRXrDSybqFEm32jwMBDzbxvCtgjU$8np3TGywbBdJAzHtu1QIyQ==".to_string());
// // let secret = None;
// print_encryption_folder_snapshot("12533251-bdd4-41f4-995f-ff12fceeaa42", secret).await;
// }

View File

@ -1,19 +1,22 @@
use uuid::Uuid;
use flowy_encrypt::{encrypt_string, generate_encrypt_secret};
use flowy_user_deps::entities::*;
use lib_infra::box_any::BoxAny;
use crate::supabase_test::util::{get_supabase_config, sign_up_param, user_auth_service};
use crate::supabase_test::util::{
get_supabase_ci_config, third_party_sign_up_param, user_auth_service,
};
// ‼️‼️‼️ Warning: this test will create a table in the database
#[tokio::test]
async fn supabase_user_sign_up_test() {
if get_supabase_config().is_none() {
if get_supabase_ci_config().is_none() {
return;
}
let user_service = user_auth_service();
let uuid = Uuid::new_v4().to_string();
let params = sign_up_param(uuid);
let params = third_party_sign_up_param(uuid);
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
assert!(!user.latest_workspace.id.is_empty());
assert!(!user.user_workspaces.is_empty());
@ -22,12 +25,12 @@ async fn supabase_user_sign_up_test() {
#[tokio::test]
async fn supabase_user_sign_up_with_existing_uuid_test() {
if get_supabase_config().is_none() {
if get_supabase_ci_config().is_none() {
return;
}
let user_service = user_auth_service();
let uuid = Uuid::new_v4().to_string();
let params = sign_up_param(uuid);
let params = third_party_sign_up_param(uuid);
let _user: SignUpResponse = user_service
.sign_up(BoxAny::new(params.clone()))
.await
@ -40,12 +43,12 @@ async fn supabase_user_sign_up_with_existing_uuid_test() {
#[tokio::test]
async fn supabase_update_user_profile_test() {
if get_supabase_config().is_none() {
if get_supabase_ci_config().is_none() {
return;
}
let user_service = user_auth_service();
let uuid = Uuid::new_v4().to_string();
let params = sign_up_param(uuid);
let params = third_party_sign_up_param(uuid);
let user: SignUpResponse = user_service
.sign_up(BoxAny::new(params.clone()))
.await
@ -55,13 +58,13 @@ async fn supabase_update_user_profile_test() {
.update_user(
UserCredentials::from_uid(user.user_id),
UpdateUserProfileParams {
id: user.user_id,
auth_type: Default::default(),
uid: user.user_id,
name: Some("123".to_string()),
email: Some(format!("{}@test.com", Uuid::new_v4())),
password: None,
icon_url: None,
openai_key: None,
encryption_sign: None,
},
)
.await
@ -78,12 +81,12 @@ async fn supabase_update_user_profile_test() {
#[tokio::test]
async fn supabase_get_user_profile_test() {
if get_supabase_config().is_none() {
if get_supabase_ci_config().is_none() {
return;
}
let user_service = user_auth_service();
let uuid = Uuid::new_v4().to_string();
let params = sign_up_param(uuid);
let params = third_party_sign_up_param(uuid);
let user: SignUpResponse = user_service
.sign_up(BoxAny::new(params.clone()))
.await
@ -99,7 +102,7 @@ async fn supabase_get_user_profile_test() {
#[tokio::test]
async fn supabase_get_not_exist_user_profile_test() {
if get_supabase_config().is_none() {
if get_supabase_ci_config().is_none() {
return;
}
@ -111,3 +114,37 @@ async fn supabase_get_not_exist_user_profile_test() {
// user not found
assert!(result.is_none());
}
#[tokio::test]
async fn user_encryption_sign_test() {
if get_supabase_ci_config().is_none() {
return;
}
let user_service = user_auth_service();
let uuid = Uuid::new_v4().to_string();
let params = third_party_sign_up_param(uuid);
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
// generate encryption sign
let secret = generate_encrypt_secret();
let sign = encrypt_string(user.user_id.to_string(), &secret).unwrap();
user_service
.update_user(
UserCredentials::from_uid(user.user_id),
UpdateUserProfileParams::new(user.user_id)
.with_encryption_type(EncryptionType::SelfEncryption(sign.clone())),
)
.await
.unwrap();
let user_profile: UserProfile = user_service
.get_user_profile(UserCredentials::from_uid(user.user_id))
.await
.unwrap()
.unwrap();
assert_eq!(
user_profile.encryption_type,
EncryptionType::SelfEncryption(sign)
);
}

View File

@ -1,66 +1,123 @@
use std::collections::HashMap;
use std::sync::Arc;
use collab::core::collab::MutexCollab;
use collab::core::origin::CollabOrigin;
use collab_plugins::cloud_storage::RemoteCollabStorage;
use uuid::Uuid;
use flowy_database_deps::cloud::DatabaseCloudService;
use flowy_folder_deps::cloud::FolderCloudService;
use flowy_folder_deps::cloud::{Folder, FolderCloudService};
use flowy_server::supabase::api::{
RESTfulPostgresServer, SupabaseCollabStorageImpl, SupabaseDatabaseServiceImpl,
SupabaseFolderServiceImpl, SupabaseServerServiceImpl, SupabaseUserServiceImpl,
};
use flowy_server::supabase::define::{USER_EMAIL, USER_UUID};
use flowy_server::supabase::define::{USER_DEVICE_ID, USER_EMAIL, USER_UUID};
use flowy_server::{AppFlowyEncryption, EncryptionImpl};
use flowy_server_config::supabase_config::SupabaseConfiguration;
use flowy_user_deps::cloud::UserService;
use crate::setup_log;
pub fn get_supabase_config() -> Option<SupabaseConfiguration> {
dotenv::from_filename("./.env.test").ok()?;
pub fn get_supabase_ci_config() -> Option<SupabaseConfiguration> {
dotenv::from_filename("./.env.ci").ok()?;
setup_log();
SupabaseConfiguration::from_env().ok()
}
#[allow(dead_code)]
pub fn get_supabase_dev_config() -> Option<SupabaseConfiguration> {
dotenv::from_filename("./.env.dev").ok()?;
setup_log();
SupabaseConfiguration::from_env().ok()
}
pub fn collab_service() -> Arc<dyn RemoteCollabStorage> {
let config = SupabaseConfiguration::from_env().unwrap();
let server = Arc::new(RESTfulPostgresServer::new(config));
let (server, encryption_impl) = appflowy_server(None);
Arc::new(SupabaseCollabStorageImpl::new(
SupabaseServerServiceImpl::new(server),
server,
None,
Arc::downgrade(&encryption_impl),
))
}
pub fn database_service() -> Arc<dyn DatabaseCloudService> {
let config = SupabaseConfiguration::from_env().unwrap();
let server = Arc::new(RESTfulPostgresServer::new(config));
Arc::new(SupabaseDatabaseServiceImpl::new(
SupabaseServerServiceImpl::new(server),
))
let (server, _encryption_impl) = appflowy_server(None);
Arc::new(SupabaseDatabaseServiceImpl::new(server))
}
pub fn user_auth_service() -> Arc<dyn UserService> {
let config = SupabaseConfiguration::from_env().unwrap();
let server = Arc::new(RESTfulPostgresServer::new(config));
Arc::new(SupabaseUserServiceImpl::new(
SupabaseServerServiceImpl::new(server),
))
let (server, _encryption_impl) = appflowy_server(None);
Arc::new(SupabaseUserServiceImpl::new(server))
}
pub fn folder_service() -> Arc<dyn FolderCloudService> {
let config = SupabaseConfiguration::from_env().unwrap();
let server = Arc::new(RESTfulPostgresServer::new(config));
Arc::new(SupabaseFolderServiceImpl::new(
SupabaseServerServiceImpl::new(server),
))
let (server, _encryption_impl) = appflowy_server(None);
Arc::new(SupabaseFolderServiceImpl::new(server))
}
pub fn sign_up_param(uuid: String) -> HashMap<String, String> {
#[allow(dead_code)]
pub fn encryption_folder_service(
secret: Option<String>,
) -> (Arc<dyn FolderCloudService>, Arc<dyn AppFlowyEncryption>) {
let (server, encryption_impl) = appflowy_server(secret);
let service = Arc::new(SupabaseFolderServiceImpl::new(server));
(service, encryption_impl)
}
pub fn encryption_collab_service(
secret: Option<String>,
) -> (Arc<dyn RemoteCollabStorage>, Arc<dyn AppFlowyEncryption>) {
let (server, encryption_impl) = appflowy_server(secret);
let service = Arc::new(SupabaseCollabStorageImpl::new(
server,
None,
Arc::downgrade(&encryption_impl),
));
(service, encryption_impl)
}
pub async fn print_encryption_folder(folder_id: &str, encryption_secret: Option<String>) {
let (cloud_service, _encryption) = encryption_folder_service(encryption_secret);
let folder_data = cloud_service.get_folder_data(folder_id).await.unwrap();
let json = serde_json::to_value(folder_data).unwrap();
println!("{}", serde_json::to_string_pretty(&json).unwrap());
}
pub async fn print_encryption_folder_snapshot(folder_id: &str, encryption_secret: Option<String>) {
let (cloud_service, _encryption) = encryption_collab_service(encryption_secret);
let snapshot = cloud_service
.get_snapshots(folder_id, 1)
.await
.pop()
.unwrap();
let collab = Arc::new(
MutexCollab::new_with_raw_data(CollabOrigin::Empty, folder_id, vec![snapshot.blob], vec![])
.unwrap(),
);
let folder_data = Folder::open(collab, None).get_folder_data().unwrap();
let json = serde_json::to_value(folder_data).unwrap();
println!("{}", serde_json::to_string_pretty(&json).unwrap());
}
pub fn appflowy_server(
encryption_secret: Option<String>,
) -> (SupabaseServerServiceImpl, Arc<dyn AppFlowyEncryption>) {
let config = SupabaseConfiguration::from_env().unwrap();
let encryption_impl: Arc<dyn AppFlowyEncryption> =
Arc::new(EncryptionImpl::new(encryption_secret));
let encryption = Arc::downgrade(&encryption_impl);
let server = Arc::new(RESTfulPostgresServer::new(config, encryption));
(SupabaseServerServiceImpl::new(server), encryption_impl)
}
pub fn third_party_sign_up_param(uuid: String) -> HashMap<String, String> {
let mut params = HashMap::new();
params.insert(USER_UUID.to_string(), uuid);
params.insert(
USER_EMAIL.to_string(),
format!("{}@test.com", Uuid::new_v4()),
);
params.insert(USER_DEVICE_ID.to_string(), Uuid::new_v4().to_string());
params
}