mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
@ -8,6 +8,7 @@ edition = "2018"
|
||||
[dependencies]
|
||||
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
|
||||
flowy-sqlite = { path = "../flowy-sqlite", optional = true }
|
||||
flowy-encrypt = { path = "../flowy-encrypt" }
|
||||
flowy-error = { path = "../flowy-error", features = ["impl_from_sqlite", "impl_from_dispatch_error"] }
|
||||
lib-infra = { path = "../../../shared-lib/lib-infra" }
|
||||
flowy-notification = { path = "../flowy-notification" }
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::convert::TryInto;
|
||||
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_user_deps::entities::*;
|
||||
|
||||
use crate::entities::parser::{UserEmail, UserIcon, UserName, UserOpenaiKey, UserPassword};
|
||||
@ -42,18 +42,42 @@ pub struct UserProfilePB {
|
||||
|
||||
#[pb(index = 7)]
|
||||
pub auth_type: AuthTypePB,
|
||||
|
||||
#[pb(index = 8)]
|
||||
pub encryption_sign: String,
|
||||
|
||||
#[pb(index = 9)]
|
||||
pub encryption_type: EncryptionTypePB,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf_Enum, Eq, PartialEq, Debug, Clone)]
|
||||
pub enum EncryptionTypePB {
|
||||
NoEncryption = 0,
|
||||
Symmetric = 1,
|
||||
}
|
||||
|
||||
impl Default for EncryptionTypePB {
|
||||
fn default() -> Self {
|
||||
Self::NoEncryption
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<UserProfile> for UserProfilePB {
|
||||
fn from(user_profile: UserProfile) -> Self {
|
||||
let (encryption_sign, encryption_ty) = match user_profile.encryption_type {
|
||||
EncryptionType::NoEncryption => ("".to_string(), EncryptionTypePB::NoEncryption),
|
||||
EncryptionType::SelfEncryption(sign) => (sign, EncryptionTypePB::Symmetric),
|
||||
};
|
||||
Self {
|
||||
id: user_profile.id,
|
||||
id: user_profile.uid,
|
||||
email: user_profile.email,
|
||||
name: user_profile.name,
|
||||
token: user_profile.token,
|
||||
icon_url: user_profile.icon_url,
|
||||
openai_key: user_profile.openai_key,
|
||||
auth_type: user_profile.auth_type.into(),
|
||||
encryption_sign,
|
||||
encryption_type: encryption_ty,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,9 +101,6 @@ pub struct UpdateUserProfilePayloadPB {
|
||||
|
||||
#[pb(index = 6, one_of)]
|
||||
pub openai_key: Option<String>,
|
||||
|
||||
#[pb(index = 7)]
|
||||
pub auth_type: AuthTypePB,
|
||||
}
|
||||
|
||||
impl UpdateUserProfilePayloadPB {
|
||||
@ -146,13 +167,13 @@ impl TryInto<UpdateUserProfileParams> for UpdateUserProfilePayloadPB {
|
||||
};
|
||||
|
||||
Ok(UpdateUserProfileParams {
|
||||
id: self.id,
|
||||
auth_type: self.auth_type.into(),
|
||||
uid: self.id,
|
||||
name,
|
||||
email,
|
||||
password,
|
||||
icon_url,
|
||||
openai_key,
|
||||
encryption_sign: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_server_config::supabase_config::SupabaseConfiguration;
|
||||
use flowy_user_deps::cloud::UserCloudConfig;
|
||||
|
||||
use crate::entities::EncryptionTypePB;
|
||||
|
||||
#[derive(ProtoBuf, Default, Debug, Clone)]
|
||||
pub struct UserPreferencesPB {
|
||||
@ -104,40 +104,53 @@ impl std::default::Default for AppearanceSettingsPB {
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct SupabaseConfigPB {
|
||||
pub struct UserCloudConfigPB {
|
||||
#[pb(index = 1)]
|
||||
supabase_url: String,
|
||||
enable_sync: bool,
|
||||
|
||||
#[pb(index = 2)]
|
||||
key: String,
|
||||
enable_encrypt: bool,
|
||||
|
||||
#[pb(index = 3)]
|
||||
jwt_secret: String,
|
||||
pub encrypt_secret: String,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct UpdateCloudConfigPB {
|
||||
#[pb(index = 1, one_of)]
|
||||
pub enable_sync: Option<bool>,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub enable_encrypt: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct UserSecretPB {
|
||||
#[pb(index = 1)]
|
||||
pub user_id: i64,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub encryption_secret: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub encryption_type: EncryptionTypePB,
|
||||
|
||||
#[pb(index = 4)]
|
||||
enable_sync: bool,
|
||||
pub encryption_sign: String,
|
||||
}
|
||||
|
||||
impl TryFrom<SupabaseConfigPB> for SupabaseConfiguration {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(config: SupabaseConfigPB) -> Result<Self, Self::Error> {
|
||||
Ok(SupabaseConfiguration {
|
||||
url: config.supabase_url,
|
||||
anon_key: config.key,
|
||||
jwt_secret: config.jwt_secret,
|
||||
enable_sync: config.enable_sync,
|
||||
})
|
||||
}
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct UserEncryptionSecretCheckPB {
|
||||
#[pb(index = 1)]
|
||||
pub is_need_secret: bool,
|
||||
}
|
||||
|
||||
impl From<SupabaseConfiguration> for SupabaseConfigPB {
|
||||
fn from(value: SupabaseConfiguration) -> Self {
|
||||
impl From<UserCloudConfig> for UserCloudConfigPB {
|
||||
fn from(value: UserCloudConfig) -> Self {
|
||||
Self {
|
||||
supabase_url: value.url,
|
||||
key: value.anon_key,
|
||||
jwt_secret: value.jwt_secret,
|
||||
enable_sync: value.enable_sync,
|
||||
enable_encrypt: value.enable_encrypt,
|
||||
encrypt_secret: value.encrypt_secret,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Weak;
|
||||
use std::{convert::TryInto, sync::Arc};
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_server_config::supabase_config::SupabaseConfiguration;
|
||||
use flowy_sqlite::kv::StorePreferences;
|
||||
use flowy_user_deps::entities::*;
|
||||
use lib_dispatch::prelude::*;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
|
||||
use crate::entities::*;
|
||||
use crate::manager::{get_supabase_config, UserManager};
|
||||
use crate::manager::UserManager;
|
||||
use crate::notification::{send_notification, UserNotification};
|
||||
use crate::services::cloud_config::{generate_cloud_config, get_cloud_config, save_cloud_config};
|
||||
|
||||
fn upgrade_manager(manager: AFPluginState<Weak<UserManager>>) -> FlowyResult<Arc<UserManager>> {
|
||||
let manager = manager
|
||||
@ -38,7 +38,6 @@ pub async fn sign_in(
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let params: SignInParams = data.into_inner().try_into()?;
|
||||
let auth_type = params.auth_type.clone();
|
||||
manager.update_auth_type(&auth_type).await;
|
||||
|
||||
let user_profile: UserProfilePB = manager
|
||||
.sign_in(BoxAny::new(params), auth_type)
|
||||
@ -64,7 +63,6 @@ pub async fn sign_up(
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let params: SignUpParams = data.into_inner().try_into()?;
|
||||
let auth_type = params.auth_type.clone();
|
||||
manager.update_auth_type(&auth_type).await;
|
||||
|
||||
let user_profile = manager.sign_up(auth_type, BoxAny::new(params)).await?;
|
||||
data_result_ok(user_profile.into())
|
||||
@ -175,28 +173,134 @@ pub async fn third_party_auth_handler(
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let params = data.into_inner();
|
||||
let auth_type: AuthType = params.auth_type.into();
|
||||
manager.update_auth_type(&auth_type).await;
|
||||
let user_profile = manager.sign_up(auth_type, BoxAny::new(params.map)).await?;
|
||||
data_result_ok(user_profile.into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub async fn set_supabase_config_handler(
|
||||
data: AFPluginData<SupabaseConfigPB>,
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub async fn set_encrypt_secret_handler(
|
||||
manager: AFPluginState<Weak<UserManager>>,
|
||||
data: AFPluginData<UserSecretPB>,
|
||||
store_preferences: AFPluginState<Weak<StorePreferences>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let config = SupabaseConfiguration::try_from(data.into_inner())?;
|
||||
manager.save_supabase_config(config);
|
||||
let store_preferences = upgrade_store_preferences(store_preferences)?;
|
||||
let data = data.into_inner();
|
||||
|
||||
let mut config = get_cloud_config(&store_preferences).unwrap_or_else(|| {
|
||||
tracing::trace!("Generate default cloud config");
|
||||
generate_cloud_config(&store_preferences)
|
||||
});
|
||||
|
||||
match data.encryption_type {
|
||||
EncryptionTypePB::NoEncryption => {
|
||||
tracing::error!("Encryption type is NoEncryption, but set encrypt secret");
|
||||
},
|
||||
EncryptionTypePB::Symmetric => {
|
||||
manager.check_encryption_sign_with_secret(
|
||||
data.user_id,
|
||||
&data.encryption_sign,
|
||||
&data.encryption_secret,
|
||||
)?;
|
||||
|
||||
config.encrypt_secret = data.encryption_secret;
|
||||
config.enable_encrypt = true;
|
||||
manager
|
||||
.set_encrypt_secret(
|
||||
data.user_id,
|
||||
config.encrypt_secret.clone(),
|
||||
EncryptionType::SelfEncryption(data.encryption_sign),
|
||||
)
|
||||
.await?;
|
||||
},
|
||||
}
|
||||
|
||||
save_cloud_config(data.user_id, &store_preferences, config)?;
|
||||
manager.resume_sign_up().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub async fn get_supabase_config_handler(
|
||||
pub async fn check_encrypt_secret_handler(
|
||||
manager: AFPluginState<Weak<UserManager>>,
|
||||
) -> DataResult<UserEncryptionSecretCheckPB, FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let uid = manager.get_session()?.user_id;
|
||||
let profile = manager.get_user_profile(uid, false).await?;
|
||||
|
||||
let is_need_secret = match profile.encryption_type {
|
||||
EncryptionType::NoEncryption => false,
|
||||
EncryptionType::SelfEncryption(sign) => {
|
||||
if sign.is_empty() {
|
||||
false
|
||||
} else {
|
||||
manager.check_encryption_sign(uid, &sign).is_err()
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
data_result_ok(UserEncryptionSecretCheckPB { is_need_secret })
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub async fn set_cloud_config_handler(
|
||||
manager: AFPluginState<Weak<UserManager>>,
|
||||
data: AFPluginData<UpdateCloudConfigPB>,
|
||||
store_preferences: AFPluginState<Weak<StorePreferences>>,
|
||||
) -> DataResult<SupabaseConfigPB, FlowyError> {
|
||||
) -> Result<(), FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let session = manager.get_session()?;
|
||||
let store_preferences = upgrade_store_preferences(store_preferences)?;
|
||||
let config = get_supabase_config(&store_preferences).unwrap_or_default();
|
||||
let update = data.into_inner();
|
||||
let mut config = get_cloud_config(&store_preferences)
|
||||
.ok_or(FlowyError::internal().context("Can't find any cloud config"))?;
|
||||
|
||||
if let Some(enable_sync) = update.enable_sync {
|
||||
manager.cloud_services.set_enable_sync(enable_sync);
|
||||
config.enable_sync = enable_sync;
|
||||
}
|
||||
|
||||
if let Some(enable_encrypt) = update.enable_encrypt {
|
||||
config.enable_encrypt = enable_encrypt;
|
||||
if enable_encrypt {
|
||||
// The encryption secret is generated when the user first enables encryption and will be
|
||||
// used to validate the encryption secret is correct when the user logs in.
|
||||
let encryption_sign =
|
||||
manager.generate_encryption_sign(session.user_id, &config.encrypt_secret)?;
|
||||
let encryption_type = EncryptionType::SelfEncryption(encryption_sign);
|
||||
manager
|
||||
.set_encrypt_secret(
|
||||
session.user_id,
|
||||
config.encrypt_secret.clone(),
|
||||
encryption_type.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let params =
|
||||
UpdateUserProfileParams::new(session.user_id).with_encryption_type(encryption_type);
|
||||
manager.update_user_profile(params).await?;
|
||||
}
|
||||
}
|
||||
|
||||
let config_pb = UserCloudConfigPB::from(config.clone());
|
||||
save_cloud_config(session.user_id, &store_preferences, config)?;
|
||||
send_notification(
|
||||
&session.user_id.to_string(),
|
||||
UserNotification::DidUpdateCloudConfig,
|
||||
)
|
||||
.payload(config_pb)
|
||||
.send();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub async fn get_cloud_config_handler(
|
||||
store_preferences: AFPluginState<Weak<StorePreferences>>,
|
||||
) -> DataResult<UserCloudConfigPB, FlowyError> {
|
||||
let store_preferences = upgrade_store_preferences(store_preferences)?;
|
||||
// Generate the default config if the config is not exist
|
||||
let config = get_cloud_config(&store_preferences)
|
||||
.unwrap_or_else(|| generate_cloud_config(&store_preferences));
|
||||
data_result_ok(config.into())
|
||||
}
|
||||
|
||||
@ -279,7 +383,9 @@ pub async fn open_historical_users_handler(
|
||||
let user = user.into_inner();
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let auth_type = AuthType::from(user.auth_type);
|
||||
manager.open_historical_user(user.user_id, user.device_id, auth_type)?;
|
||||
manager
|
||||
.open_historical_user(user.user_id, user.device_id, auth_type)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@ use strum_macros::Display;
|
||||
|
||||
use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_server_config::supabase_config::SupabaseConfiguration;
|
||||
use flowy_user_deps::cloud::UserService;
|
||||
use flowy_user_deps::entities::*;
|
||||
use lib_dispatch::prelude::*;
|
||||
@ -35,8 +34,10 @@ pub fn init(user_session: Weak<UserManager>) -> AFPlugin {
|
||||
.event(UserEvent::SetAppearanceSetting, set_appearance_setting)
|
||||
.event(UserEvent::GetAppearanceSetting, get_appearance_setting)
|
||||
.event(UserEvent::GetUserSetting, get_user_setting)
|
||||
.event(UserEvent::SetSupabaseConfig, set_supabase_config_handler)
|
||||
.event(UserEvent::GetSupabaseConfig, get_supabase_config_handler)
|
||||
.event(UserEvent::SetCloudConfig, set_cloud_config_handler)
|
||||
.event(UserEvent::GetCloudConfig, get_cloud_config_handler)
|
||||
.event(UserEvent::SetEncryptionSecret, set_encrypt_secret_handler)
|
||||
.event(UserEvent::CheckEncryptionSign, check_encrypt_secret_handler)
|
||||
.event(UserEvent::ThirdPartyAuth, third_party_auth_handler)
|
||||
.event(
|
||||
UserEvent::GetAllUserWorkspaces,
|
||||
@ -101,7 +102,8 @@ pub trait UserStatusCallback: Send + Sync + 'static {
|
||||
/// The user cloud service provider.
|
||||
/// The provider can be supabase, firebase, aws, or any other cloud service.
|
||||
pub trait UserCloudServiceProvider: Send + Sync + 'static {
|
||||
fn set_supabase_config(&self, supabase_config: &SupabaseConfiguration);
|
||||
fn set_enable_sync(&self, enable_sync: bool);
|
||||
fn set_encrypt_secret(&self, secret: String);
|
||||
fn set_auth_type(&self, auth_type: AuthType);
|
||||
fn set_device_id(&self, device_id: &str);
|
||||
fn get_user_service(&self) -> Result<Arc<dyn UserService>, FlowyError>;
|
||||
@ -112,8 +114,12 @@ impl<T> UserCloudServiceProvider for Arc<T>
|
||||
where
|
||||
T: UserCloudServiceProvider,
|
||||
{
|
||||
fn set_supabase_config(&self, supabase_config: &SupabaseConfiguration) {
|
||||
(**self).set_supabase_config(supabase_config)
|
||||
fn set_enable_sync(&self, enable_sync: bool) {
|
||||
(**self).set_enable_sync(enable_sync)
|
||||
}
|
||||
|
||||
fn set_encrypt_secret(&self, secret: String) {
|
||||
(**self).set_encrypt_secret(secret)
|
||||
}
|
||||
|
||||
fn set_auth_type(&self, auth_type: AuthType) {
|
||||
@ -221,13 +227,17 @@ pub enum UserEvent {
|
||||
#[event(input = "ThirdPartyAuthPB", output = "UserProfilePB")]
|
||||
ThirdPartyAuth = 10,
|
||||
|
||||
/// Set the supabase config. It will be written to the environment variables.
|
||||
/// Check out the `write_to_env` of [SupabaseConfigPB].
|
||||
#[event(input = "SupabaseConfigPB")]
|
||||
SetSupabaseConfig = 13,
|
||||
#[event(input = "UpdateCloudConfigPB")]
|
||||
SetCloudConfig = 13,
|
||||
|
||||
#[event(output = "SupabaseConfigPB")]
|
||||
GetSupabaseConfig = 14,
|
||||
#[event(output = "UserCloudConfigPB")]
|
||||
GetCloudConfig = 14,
|
||||
|
||||
#[event(input = "UserSecretPB")]
|
||||
SetEncryptionSecret = 15,
|
||||
|
||||
#[event(output = "UserEncryptionSecretCheckPB")]
|
||||
CheckEncryptionSign = 16,
|
||||
|
||||
/// Return the all the workspaces of the user
|
||||
#[event()]
|
||||
|
@ -9,8 +9,7 @@ use serde_json::Value;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use uuid::Uuid;
|
||||
|
||||
use flowy_error::{internal_error, ErrorCode};
|
||||
use flowy_server_config::supabase_config::SupabaseConfiguration;
|
||||
use flowy_error::{internal_error, ErrorCode, FlowyResult};
|
||||
use flowy_sqlite::kv::StorePreferences;
|
||||
use flowy_sqlite::schema::user_table;
|
||||
use flowy_sqlite::ConnectionPool;
|
||||
@ -25,16 +24,15 @@ use crate::event_map::{
|
||||
use crate::migrations::historical_document::HistoricalEmptyDocumentMigration;
|
||||
use crate::migrations::local_user_to_cloud::migration_user_to_cloud;
|
||||
use crate::migrations::migration::UserLocalDataMigration;
|
||||
use crate::migrations::UserMigrationContext;
|
||||
use crate::migrations::MigrationUser;
|
||||
use crate::services::cloud_config::remove_cloud_config;
|
||||
use crate::services::database::UserDB;
|
||||
use crate::services::entities::Session;
|
||||
use crate::services::entities::{ResumableSignUp, Session};
|
||||
use crate::services::user_awareness::UserAwarenessDataSource;
|
||||
use crate::services::user_sql::{UserTable, UserTableChangeset};
|
||||
use crate::services::user_workspace::save_user_workspaces;
|
||||
use crate::{errors::FlowyError, notification::*};
|
||||
|
||||
const SUPABASE_CONFIG_CACHE_KEY: &str = "af_supabase_config";
|
||||
|
||||
pub struct UserSessionConfig {
|
||||
root_dir: String,
|
||||
|
||||
@ -62,6 +60,7 @@ pub struct UserManager {
|
||||
pub(crate) user_awareness: Arc<Mutex<Option<MutexUserAwareness>>>,
|
||||
pub(crate) user_status_callback: RwLock<Arc<dyn UserStatusCallback>>,
|
||||
pub(crate) collab_builder: Weak<AppFlowyCollabBuilder>,
|
||||
resumable_sign_up: Mutex<Option<ResumableSignUp>>,
|
||||
}
|
||||
|
||||
impl UserManager {
|
||||
@ -82,6 +81,7 @@ impl UserManager {
|
||||
user_awareness: Arc::new(Default::default()),
|
||||
user_status_callback,
|
||||
collab_builder,
|
||||
resumable_sign_up: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,29 +160,18 @@ impl UserManager {
|
||||
params: BoxAny,
|
||||
auth_type: AuthType,
|
||||
) -> Result<UserProfile, FlowyError> {
|
||||
self.update_auth_type(&auth_type).await;
|
||||
let response: SignInResponse = self
|
||||
.cloud_services
|
||||
.get_user_service()?
|
||||
.sign_in(params)
|
||||
.await?;
|
||||
let session: Session = response.clone().into();
|
||||
let uid = session.user_id;
|
||||
let device_id = session.device_id.clone();
|
||||
let session = Session::from(&response);
|
||||
self.set_collab_config(&session);
|
||||
self.set_current_session(Some(session.clone()))?;
|
||||
self.log_historical_user(
|
||||
uid,
|
||||
&response.device_id,
|
||||
response.name.clone(),
|
||||
&auth_type,
|
||||
self.user_dir(uid),
|
||||
);
|
||||
let user_workspace = response.latest_workspace.clone();
|
||||
save_user_workspaces(uid, self.db_pool(uid)?, &response.user_workspaces)?;
|
||||
let user_profile: UserProfile = self
|
||||
.save_user(uid, (response, auth_type).into())
|
||||
.await?
|
||||
.into();
|
||||
|
||||
let latest_workspace = response.latest_workspace.clone();
|
||||
let user_profile = UserProfile::from((&response, &auth_type));
|
||||
self.save_auth_data(&response, &auth_type, &session).await?;
|
||||
let _ = self
|
||||
.initialize_user_awareness(&session, UserAwarenessDataSource::Remote)
|
||||
.await;
|
||||
@ -191,25 +180,23 @@ impl UserManager {
|
||||
.user_status_callback
|
||||
.read()
|
||||
.await
|
||||
.did_sign_in(user_profile.id, &user_workspace, &device_id)
|
||||
.did_sign_in(user_profile.uid, &latest_workspace, &session.device_id)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to call did_sign_in callback: {:?}", e);
|
||||
}
|
||||
|
||||
send_sign_in_notification()
|
||||
.payload::<UserProfilePB>(user_profile.clone().into())
|
||||
.send();
|
||||
Ok(user_profile)
|
||||
}
|
||||
|
||||
pub async fn update_auth_type(&self, auth_type: &AuthType) {
|
||||
pub(crate) async fn update_auth_type(&self, auth_type: &AuthType) {
|
||||
self
|
||||
.user_status_callback
|
||||
.read()
|
||||
.await
|
||||
.auth_type_did_changed(auth_type.clone());
|
||||
|
||||
self.cloud_services.set_auth_type(auth_type.clone());
|
||||
}
|
||||
|
||||
@ -220,94 +207,117 @@ impl UserManager {
|
||||
/// and saving workspace information. If a user is signing up with a new profile and previously had guest data,
|
||||
/// this function may migrate that data over to the new account.
|
||||
///
|
||||
#[tracing::instrument(level = "debug", skip(self, params))]
|
||||
#[tracing::instrument(level = "info", skip(self, params))]
|
||||
pub async fn sign_up(
|
||||
&self,
|
||||
auth_type: AuthType,
|
||||
params: BoxAny,
|
||||
) -> Result<UserProfile, FlowyError> {
|
||||
let old_user = {
|
||||
if let Ok(old_session) = self.get_session() {
|
||||
self
|
||||
.get_user_profile(old_session.user_id, false)
|
||||
.await
|
||||
.ok()
|
||||
.map(|user_profile| UserMigrationContext {
|
||||
user_profile,
|
||||
session: old_session,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
remove_cloud_config(&self.store_preferences);
|
||||
self.update_auth_type(&auth_type).await;
|
||||
|
||||
let migration_user = self.get_migration_user(&auth_type).await;
|
||||
let auth_service = self.cloud_services.get_user_service()?;
|
||||
let response: SignUpResponse = auth_service.sign_up(params).await?;
|
||||
let mut sign_up_context = SignUpContext {
|
||||
is_new: response.is_new,
|
||||
local_folder: None,
|
||||
};
|
||||
let user_profile = UserProfile::from((&response, &auth_type));
|
||||
if user_profile.encryption_type.is_need_encrypt_secret() {
|
||||
self
|
||||
.resumable_sign_up
|
||||
.lock()
|
||||
.await
|
||||
.replace(ResumableSignUp {
|
||||
user_profile: user_profile.clone(),
|
||||
migration_user,
|
||||
response,
|
||||
auth_type,
|
||||
});
|
||||
} else {
|
||||
self
|
||||
.continue_sign_up(&user_profile, migration_user, response, &auth_type)
|
||||
.await?;
|
||||
}
|
||||
Ok(user_profile)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "info", skip(self))]
|
||||
pub async fn resume_sign_up(&self) -> Result<(), FlowyError> {
|
||||
let ResumableSignUp {
|
||||
user_profile,
|
||||
migration_user,
|
||||
response,
|
||||
auth_type,
|
||||
} = self
|
||||
.resumable_sign_up
|
||||
.lock()
|
||||
.await
|
||||
.clone()
|
||||
.ok_or(FlowyError::new(
|
||||
ErrorCode::Internal,
|
||||
"No resumable sign up data",
|
||||
))?;
|
||||
self
|
||||
.continue_sign_up(&user_profile, migration_user, response, &auth_type)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "info", skip_all, err)]
|
||||
async fn continue_sign_up(
|
||||
&self,
|
||||
user_profile: &UserProfile,
|
||||
migration_user: Option<MigrationUser>,
|
||||
response: SignUpResponse,
|
||||
auth_type: &AuthType,
|
||||
) -> FlowyResult<()> {
|
||||
let new_session = Session::from(&response);
|
||||
self.set_current_session(Some(new_session.clone()))?;
|
||||
self.set_collab_config(&new_session);
|
||||
let uid = response.user_id;
|
||||
self.log_historical_user(
|
||||
uid,
|
||||
&response.device_id,
|
||||
response.name.clone(),
|
||||
&auth_type,
|
||||
self.user_dir(uid),
|
||||
);
|
||||
save_user_workspaces(uid, self.db_pool(uid)?, &response.user_workspaces)?;
|
||||
let new_user_profile: UserProfile = self
|
||||
.save_user(uid, (response, auth_type.clone()).into())
|
||||
.await?
|
||||
.into();
|
||||
let user_awareness_source = if sign_up_context.is_new {
|
||||
|
||||
let user_awareness_source = if response.is_new_user {
|
||||
UserAwarenessDataSource::Local
|
||||
} else {
|
||||
UserAwarenessDataSource::Remote
|
||||
};
|
||||
// Only migrate the data if the user is login in as a guest and sign up as a new user if the current
|
||||
// auth type is not [AuthType::Local].
|
||||
if sign_up_context.is_new {
|
||||
if let Some(old_user) = old_user {
|
||||
if old_user.user_profile.auth_type == AuthType::Local && !auth_type.is_local() {
|
||||
let new_user = UserMigrationContext {
|
||||
user_profile: new_user_profile.clone(),
|
||||
session: new_session.clone(),
|
||||
};
|
||||
tracing::info!(
|
||||
"Migrate old user data from {:?} to {:?}",
|
||||
old_user.user_profile.id,
|
||||
new_user.user_profile.id
|
||||
);
|
||||
match self.migrate_local_user_to_cloud(&old_user, &new_user).await {
|
||||
Ok(folder_data) => sign_up_context.local_folder = folder_data,
|
||||
Err(e) => tracing::error!("{:?}", e),
|
||||
}
|
||||
// close the old user db
|
||||
let _ = self.database.close(old_user.session.user_id);
|
||||
let mut sign_up_context = SignUpContext {
|
||||
is_new: response.is_new_user,
|
||||
local_folder: None,
|
||||
};
|
||||
if response.is_new_user {
|
||||
if let Some(old_user) = migration_user {
|
||||
let new_user = MigrationUser {
|
||||
user_profile: user_profile.clone(),
|
||||
session: new_session.clone(),
|
||||
};
|
||||
tracing::info!(
|
||||
"Migrate old user data from {:?} to {:?}",
|
||||
old_user.user_profile.uid,
|
||||
new_user.user_profile.uid
|
||||
);
|
||||
match self.migrate_local_user_to_cloud(&old_user, &new_user).await {
|
||||
Ok(folder_data) => sign_up_context.local_folder = folder_data,
|
||||
Err(e) => tracing::error!("{:?}", e),
|
||||
}
|
||||
let _ = self.database.close(old_user.session.user_id);
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
.initialize_user_awareness(&new_session, user_awareness_source)
|
||||
.await;
|
||||
|
||||
let _ = self
|
||||
self
|
||||
.user_status_callback
|
||||
.read()
|
||||
.await
|
||||
.did_sign_up(
|
||||
sign_up_context,
|
||||
&new_user_profile,
|
||||
user_profile,
|
||||
&new_session.user_workspace,
|
||||
&new_session.device_id,
|
||||
)
|
||||
.await;
|
||||
Ok(new_user_profile)
|
||||
.await?;
|
||||
self
|
||||
.save_auth_data(&response, auth_type, &new_session)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "info", skip(self))]
|
||||
@ -315,6 +325,7 @@ impl UserManager {
|
||||
let session = self.get_session()?;
|
||||
self.database.close(session.user_id)?;
|
||||
self.set_current_session(None)?;
|
||||
remove_cloud_config(&self.store_preferences);
|
||||
|
||||
let server = self.cloud_services.get_user_service()?;
|
||||
tokio::spawn(async move {
|
||||
@ -337,7 +348,8 @@ impl UserManager {
|
||||
&self,
|
||||
params: UpdateUserProfileParams,
|
||||
) -> Result<(), FlowyError> {
|
||||
let auth_type = params.auth_type.clone();
|
||||
let old_user_profile = self.get_user_profile(params.uid, false).await?;
|
||||
let auth_type = old_user_profile.auth_type.clone();
|
||||
let session = self.get_session()?;
|
||||
let changeset = UserTableChangeset::new(params.clone());
|
||||
diesel_update_table!(
|
||||
@ -347,13 +359,12 @@ impl UserManager {
|
||||
);
|
||||
|
||||
let session = self.get_session()?;
|
||||
let user_profile = self.get_user_profile(session.user_id, false).await?;
|
||||
let profile_pb: UserProfilePB = user_profile.into();
|
||||
let new_user_profile = self.get_user_profile(session.user_id, false).await?;
|
||||
send_notification(
|
||||
&session.user_id.to_string(),
|
||||
UserNotification::DidUpdateUserProfile,
|
||||
)
|
||||
.payload(profile_pb)
|
||||
.payload(UserProfilePB::from(new_user_profile))
|
||||
.send();
|
||||
self
|
||||
.update_user(&auth_type, session.user_id, None, params)
|
||||
@ -441,13 +452,6 @@ impl UserManager {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn save_supabase_config(&self, config: SupabaseConfiguration) {
|
||||
self.cloud_services.set_supabase_config(&config);
|
||||
let _ = self
|
||||
.store_preferences
|
||||
.set_object(SUPABASE_CONFIG_CACHE_KEY, config);
|
||||
}
|
||||
|
||||
async fn update_user(
|
||||
&self,
|
||||
_auth_type: &AuthType,
|
||||
@ -466,7 +470,7 @@ impl UserManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn save_user(&self, uid: i64, user: UserTable) -> Result<UserTable, FlowyError> {
|
||||
async fn save_user(&self, uid: i64, user: UserTable) -> Result<(), FlowyError> {
|
||||
let conn = self.db_connection(uid)?;
|
||||
conn.immediate_transaction(|| {
|
||||
// delete old user if exists
|
||||
@ -474,12 +478,12 @@ impl UserManager {
|
||||
.execute(&*conn)?;
|
||||
|
||||
let _ = diesel::insert_into(user_table::table)
|
||||
.values(user.clone())
|
||||
.values(user)
|
||||
.execute(&*conn)?;
|
||||
Ok::<(), FlowyError>(())
|
||||
})?;
|
||||
|
||||
Ok(user)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn set_current_session(&self, session: Option<Session>) -> Result<(), FlowyError> {
|
||||
@ -520,6 +524,29 @@ impl UserManager {
|
||||
}
|
||||
}
|
||||
|
||||
async fn save_auth_data(
|
||||
&self,
|
||||
response: &impl UserAuthResponse,
|
||||
auth_type: &AuthType,
|
||||
session: &Session,
|
||||
) -> Result<(), FlowyError> {
|
||||
let user_profile = UserProfile::from((response, auth_type));
|
||||
let uid = user_profile.uid;
|
||||
self.add_historical_user(
|
||||
uid,
|
||||
response.device_id(),
|
||||
response.user_name().to_string(),
|
||||
auth_type,
|
||||
self.user_dir(uid),
|
||||
);
|
||||
save_user_workspaces(uid, self.db_pool(uid)?, response.user_workspaces())?;
|
||||
self
|
||||
.save_user(uid, (user_profile, auth_type.clone()).into())
|
||||
.await?;
|
||||
self.set_current_session(Some(session.clone()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_collab_config(&self, session: &Session) {
|
||||
let collab_builder = self.collab_builder.upgrade().unwrap();
|
||||
collab_builder.set_sync_device(session.device_id.clone());
|
||||
@ -529,21 +556,18 @@ impl UserManager {
|
||||
|
||||
async fn migrate_local_user_to_cloud(
|
||||
&self,
|
||||
old_user: &UserMigrationContext,
|
||||
new_user: &UserMigrationContext,
|
||||
old_user: &MigrationUser,
|
||||
new_user: &MigrationUser,
|
||||
) -> Result<Option<FolderData>, FlowyError> {
|
||||
let old_collab_db = self.database.get_collab_db(old_user.session.user_id)?;
|
||||
let new_collab_db = self.database.get_collab_db(new_user.session.user_id)?;
|
||||
let folder_data = migration_user_to_cloud(old_user, &old_collab_db, new_user, &new_collab_db)?;
|
||||
// Save the old user workspace setting.
|
||||
save_user_workspaces(
|
||||
old_user.session.user_id,
|
||||
self.database.get_pool(old_user.session.user_id)?,
|
||||
&[old_user.session.user_workspace.clone()],
|
||||
)?;
|
||||
Ok(folder_data)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_supabase_config(
|
||||
store_preference: &Arc<StorePreferences>,
|
||||
) -> Option<SupabaseConfiguration> {
|
||||
store_preference
|
||||
.get_str(SUPABASE_CONFIG_CACHE_KEY)
|
||||
.and_then(|s| serde_json::from_str(&s).ok())
|
||||
.unwrap_or_else(|| SupabaseConfiguration::from_env().ok())
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ use flowy_user_deps::entities::UserProfile;
|
||||
|
||||
use crate::services::entities::Session;
|
||||
|
||||
pub struct UserMigrationContext {
|
||||
#[derive(Clone)]
|
||||
pub struct MigrationUser {
|
||||
pub user_profile: UserProfile,
|
||||
pub session: Session,
|
||||
}
|
||||
|
@ -8,14 +8,14 @@ use collab_folder::core::{Folder, FolderData};
|
||||
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
|
||||
use crate::migrations::UserMigrationContext;
|
||||
use crate::migrations::MigrationUser;
|
||||
|
||||
/// Migration the collab objects of the old user to new user. Currently, it only happens when
|
||||
/// the user is a local user and try to use AppFlowy cloud service.
|
||||
pub fn migration_user_to_cloud(
|
||||
old_user: &UserMigrationContext,
|
||||
old_user: &MigrationUser,
|
||||
old_collab_db: &Arc<RocksCollabDB>,
|
||||
new_user: &UserMigrationContext,
|
||||
new_user: &MigrationUser,
|
||||
new_collab_db: &Arc<RocksCollabDB>,
|
||||
) -> FlowyResult<Option<FolderData>> {
|
||||
let mut folder_data = None;
|
||||
|
@ -10,6 +10,7 @@ pub(crate) enum UserNotification {
|
||||
DidUserSignIn = 1,
|
||||
DidUpdateUserProfile = 2,
|
||||
DidUpdateUserWorkspaces = 3,
|
||||
DidUpdateCloudConfig = 4,
|
||||
}
|
||||
|
||||
impl std::convert::From<UserNotification> for i32 {
|
||||
|
48
frontend/rust-lib/flowy-user/src/services/cloud_config.rs
Normal file
48
frontend/rust-lib/flowy-user/src/services/cloud_config.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use flowy_encrypt::generate_encrypt_secret;
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_sqlite::kv::StorePreferences;
|
||||
use flowy_user_deps::cloud::UserCloudConfig;
|
||||
|
||||
const CLOUD_CONFIG_KEY: &str = "af_user_cloud_config";
|
||||
|
||||
pub fn generate_cloud_config(store_preference: &Arc<StorePreferences>) -> UserCloudConfig {
|
||||
let config = UserCloudConfig::new(generate_encrypt_secret());
|
||||
let key = cache_key_for_cloud_config();
|
||||
store_preference.set_object(&key, config.clone()).unwrap();
|
||||
config
|
||||
}
|
||||
|
||||
pub fn remove_cloud_config(store_preference: &Arc<StorePreferences>) {
|
||||
let key = cache_key_for_cloud_config();
|
||||
store_preference.remove(&key);
|
||||
}
|
||||
|
||||
pub fn save_cloud_config(
|
||||
uid: i64,
|
||||
store_preference: &Arc<StorePreferences>,
|
||||
config: UserCloudConfig,
|
||||
) -> FlowyResult<()> {
|
||||
let encrypt_secret = config.encrypt_secret.clone();
|
||||
let key = cache_key_for_cloud_config();
|
||||
store_preference.set_object(&key, config)?;
|
||||
store_preference.set_object(&format!("{}-encrypt-secret", uid), encrypt_secret)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cache_key_for_cloud_config() -> String {
|
||||
CLOUD_CONFIG_KEY.to_string()
|
||||
}
|
||||
|
||||
pub fn get_cloud_config(store_preference: &Arc<StorePreferences>) -> Option<UserCloudConfig> {
|
||||
let key = cache_key_for_cloud_config();
|
||||
store_preference.get_object::<UserCloudConfig>(&key)
|
||||
}
|
||||
|
||||
pub fn get_encrypt_secret(store_preference: &Arc<StorePreferences>) -> Option<String> {
|
||||
let key = cache_key_for_cloud_config();
|
||||
store_preference
|
||||
.get_object::<UserCloudConfig>(&key)
|
||||
.map(|config| config.encrypt_secret)
|
||||
}
|
@ -7,10 +7,11 @@ use serde::de::{Deserializer, MapAccess, Visitor};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use flowy_user_deps::entities::AuthType;
|
||||
use flowy_user_deps::entities::{SignInResponse, SignUpResponse, UserWorkspace};
|
||||
use flowy_user_deps::entities::{AuthType, UserAuthResponse};
|
||||
use flowy_user_deps::entities::{SignUpResponse, UserProfile, UserWorkspace};
|
||||
|
||||
use crate::entities::AuthTypePB;
|
||||
use crate::migrations::MigrationUser;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Session {
|
||||
@ -89,12 +90,15 @@ impl<'de> Deserialize<'de> for Session {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<SignInResponse> for Session {
|
||||
fn from(resp: SignInResponse) -> Self {
|
||||
Session {
|
||||
user_id: resp.user_id,
|
||||
device_id: resp.device_id,
|
||||
user_workspace: resp.latest_workspace,
|
||||
impl<T> From<&T> for Session
|
||||
where
|
||||
T: UserAuthResponse,
|
||||
{
|
||||
fn from(value: &T) -> Self {
|
||||
Self {
|
||||
user_id: value.user_id(),
|
||||
device_id: value.device_id().to_string(),
|
||||
user_workspace: value.latest_workspace().clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,16 +115,6 @@ impl std::convert::From<Session> for String {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SignUpResponse> for Session {
|
||||
fn from(value: &SignUpResponse) -> Self {
|
||||
Session {
|
||||
user_id: value.user_id,
|
||||
device_id: value.device_id.clone(),
|
||||
user_workspace: value.latest_workspace.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
@ -208,3 +202,11 @@ pub struct HistoricalUser {
|
||||
pub device_id: String,
|
||||
}
|
||||
const DEFAULT_AUTH_TYPE: fn() -> AuthType = || AuthType::Local;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ResumableSignUp {
|
||||
pub user_profile: UserProfile,
|
||||
pub response: SignUpResponse,
|
||||
pub auth_type: AuthType,
|
||||
pub migration_user: Option<MigrationUser>,
|
||||
}
|
||||
|
@ -7,11 +7,26 @@ use flowy_user_deps::entities::{AuthType, UserWorkspace};
|
||||
use lib_infra::util::timestamp;
|
||||
|
||||
use crate::manager::UserManager;
|
||||
use crate::migrations::MigrationUser;
|
||||
use crate::services::entities::{HistoricalUser, HistoricalUsers, Session};
|
||||
use crate::services::user_workspace_sql::UserWorkspaceTable;
|
||||
|
||||
const HISTORICAL_USER: &str = "af_historical_users";
|
||||
impl UserManager {
|
||||
pub async fn get_migration_user(&self, auth_type: &AuthType) -> Option<MigrationUser> {
|
||||
// Only migrate the data if the user is login in as a guest and sign up as a new user if the current
|
||||
// auth type is not [AuthType::Local].
|
||||
let session = self.get_session().ok()?;
|
||||
let user_profile = self.get_user_profile(session.user_id, false).await.ok()?;
|
||||
if user_profile.auth_type == AuthType::Local && !auth_type.is_local() {
|
||||
Some(MigrationUser {
|
||||
user_profile,
|
||||
session,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// Logs a user's details for historical tracking.
|
||||
///
|
||||
/// This function adds a user's details to a local historical tracking system, useful for
|
||||
@ -24,7 +39,7 @@ impl UserManager {
|
||||
/// - `auth_type`: The type of authentication used.
|
||||
/// - `storage_path`: Path where user data is stored.
|
||||
///
|
||||
pub fn log_historical_user(
|
||||
pub fn add_historical_user(
|
||||
&self,
|
||||
uid: i64,
|
||||
device_id: &str,
|
||||
@ -67,12 +82,14 @@ impl UserManager {
|
||||
/// This function facilitates the re-opening of a user's session from historical tracking.
|
||||
/// It retrieves the user's workspace and establishes a new session for the user.
|
||||
///
|
||||
pub fn open_historical_user(
|
||||
pub async fn open_historical_user(
|
||||
&self,
|
||||
uid: i64,
|
||||
device_id: String,
|
||||
auth_type: AuthType,
|
||||
) -> FlowyResult<()> {
|
||||
debug_assert!(auth_type.is_local());
|
||||
self.update_auth_type(&auth_type).await;
|
||||
let conn = self.db_connection(uid)?;
|
||||
let row = user_workspace_table::dsl::user_workspace_table
|
||||
.filter(user_workspace_table::uid.eq(uid))
|
||||
@ -83,8 +100,6 @@ impl UserManager {
|
||||
device_id,
|
||||
user_workspace,
|
||||
};
|
||||
debug_assert!(auth_type.is_local());
|
||||
self.cloud_services.set_auth_type(auth_type);
|
||||
self.set_current_session(Some(session))?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
pub mod cloud_config;
|
||||
pub mod database;
|
||||
pub mod entities;
|
||||
pub(crate) mod historical_user;
|
||||
pub(crate) mod user_awareness;
|
||||
pub(crate) mod user_encryption;
|
||||
pub(crate) mod user_sql;
|
||||
pub(crate) mod user_workspace;
|
||||
pub(crate) mod user_workspace_sql;
|
||||
|
62
frontend/rust-lib/flowy-user/src/services/user_encryption.rs
Normal file
62
frontend/rust-lib/flowy-user/src/services/user_encryption.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use flowy_encrypt::{decrypt_string, encrypt_string};
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_user_deps::entities::{EncryptionType, UpdateUserProfileParams, UserCredentials};
|
||||
|
||||
use crate::manager::UserManager;
|
||||
use crate::services::cloud_config::get_encrypt_secret;
|
||||
|
||||
impl UserManager {
|
||||
pub async fn set_encrypt_secret(
|
||||
&self,
|
||||
uid: i64,
|
||||
secret: String,
|
||||
encryption_type: EncryptionType,
|
||||
) -> FlowyResult<()> {
|
||||
let params = UpdateUserProfileParams::new(uid).with_encryption_type(encryption_type);
|
||||
self
|
||||
.cloud_services
|
||||
.get_user_service()?
|
||||
.update_user(UserCredentials::from_uid(uid), params.clone())
|
||||
.await?;
|
||||
self.cloud_services.set_encrypt_secret(secret);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_encryption_sign(&self, uid: i64, encrypt_secret: &str) -> FlowyResult<String> {
|
||||
let encrypt_sign = encrypt_string(uid.to_string(), encrypt_secret)?;
|
||||
Ok(encrypt_sign)
|
||||
}
|
||||
|
||||
pub fn check_encryption_sign(&self, uid: i64, encrypt_sign: &str) -> FlowyResult<()> {
|
||||
let store_preference = self
|
||||
.get_store_preferences()
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::new(
|
||||
ErrorCode::Internal,
|
||||
"Failed to get store preference",
|
||||
))?;
|
||||
|
||||
let encrypt_secret = get_encrypt_secret(&store_preference).ok_or(FlowyError::new(
|
||||
ErrorCode::Internal,
|
||||
"Encrypt secret is not set",
|
||||
))?;
|
||||
|
||||
self.check_encryption_sign_with_secret(uid, encrypt_sign, &encrypt_secret)
|
||||
}
|
||||
|
||||
pub fn check_encryption_sign_with_secret(
|
||||
&self,
|
||||
uid: i64,
|
||||
encrypt_sign: &str,
|
||||
encryption_secret: &str,
|
||||
) -> FlowyResult<()> {
|
||||
let decrypt_str = decrypt_string(encrypt_sign, encryption_secret)
|
||||
.map_err(|_| FlowyError::new(ErrorCode::InvalidEncryptSecret, "Invalid decryption secret"))?;
|
||||
if uid.to_string() == decrypt_str {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ErrorCode::InvalidEncryptSecret.into())
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use flowy_sqlite::schema::user_table;
|
||||
use flowy_user_deps::entities::*;
|
||||
|
||||
@ -14,6 +16,7 @@ pub struct UserTable {
|
||||
pub(crate) token: String,
|
||||
pub(crate) email: String,
|
||||
pub(crate) auth_type: i32,
|
||||
pub(crate) encryption_type: String,
|
||||
}
|
||||
|
||||
impl UserTable {
|
||||
@ -23,35 +26,20 @@ impl UserTable {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(SignUpResponse, AuthType)> for UserTable {
|
||||
fn from(params: (SignUpResponse, AuthType)) -> Self {
|
||||
let resp = params.0;
|
||||
impl From<(UserProfile, AuthType)> for UserTable {
|
||||
fn from(value: (UserProfile, AuthType)) -> Self {
|
||||
let (user_profile, auth_type) = value;
|
||||
let encryption_type = serde_json::to_string(&user_profile.encryption_type).unwrap_or_default();
|
||||
UserTable {
|
||||
id: resp.user_id.to_string(),
|
||||
name: resp.name,
|
||||
token: resp.token.unwrap_or_default(),
|
||||
email: resp.email.unwrap_or_default(),
|
||||
workspace: resp.latest_workspace.id,
|
||||
icon_url: "".to_string(),
|
||||
openai_key: "".to_string(),
|
||||
auth_type: params.1 as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(SignInResponse, AuthType)> for UserTable {
|
||||
fn from(params: (SignInResponse, AuthType)) -> Self {
|
||||
let resp = params.0;
|
||||
let auth_type = params.1;
|
||||
UserTable {
|
||||
id: resp.user_id.to_string(),
|
||||
name: resp.name,
|
||||
token: resp.token.unwrap_or_default(),
|
||||
email: resp.email.unwrap_or_default(),
|
||||
workspace: resp.latest_workspace.id,
|
||||
icon_url: "".to_string(),
|
||||
openai_key: "".to_string(),
|
||||
id: user_profile.uid.to_string(),
|
||||
name: user_profile.name,
|
||||
workspace: user_profile.workspace_id,
|
||||
icon_url: user_profile.icon_url,
|
||||
openai_key: user_profile.openai_key,
|
||||
token: user_profile.token,
|
||||
email: user_profile.email,
|
||||
auth_type: auth_type as i32,
|
||||
encryption_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -59,7 +47,7 @@ impl From<(SignInResponse, AuthType)> for UserTable {
|
||||
impl From<UserTable> for UserProfile {
|
||||
fn from(table: UserTable) -> Self {
|
||||
UserProfile {
|
||||
id: table.id.parse::<i64>().unwrap_or(0),
|
||||
uid: table.id.parse::<i64>().unwrap_or(0),
|
||||
email: table.email,
|
||||
name: table.name,
|
||||
token: table.token,
|
||||
@ -67,6 +55,7 @@ impl From<UserTable> for UserProfile {
|
||||
openai_key: table.openai_key,
|
||||
workspace_id: table.workspace,
|
||||
auth_type: AuthType::from(table.auth_type),
|
||||
encryption_type: EncryptionType::from_str(&table.encryption_type).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,28 +69,36 @@ pub struct UserTableChangeset {
|
||||
pub email: Option<String>,
|
||||
pub icon_url: Option<String>,
|
||||
pub openai_key: Option<String>,
|
||||
pub encryption_type: Option<String>,
|
||||
}
|
||||
|
||||
impl UserTableChangeset {
|
||||
pub fn new(params: UpdateUserProfileParams) -> Self {
|
||||
let encryption_type = params.encryption_sign.map(|sign| {
|
||||
let ty = EncryptionType::from_sign(&sign);
|
||||
serde_json::to_string(&ty).unwrap_or_default()
|
||||
});
|
||||
UserTableChangeset {
|
||||
id: params.id.to_string(),
|
||||
id: params.uid.to_string(),
|
||||
workspace: None,
|
||||
name: params.name,
|
||||
email: params.email,
|
||||
icon_url: params.icon_url,
|
||||
openai_key: params.openai_key,
|
||||
encryption_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_user_profile(user_profile: UserProfile) -> Self {
|
||||
let encryption_type = serde_json::to_string(&user_profile.encryption_type).unwrap_or_default();
|
||||
UserTableChangeset {
|
||||
id: user_profile.id.to_string(),
|
||||
id: user_profile.uid.to_string(),
|
||||
workspace: None,
|
||||
name: Some(user_profile.name),
|
||||
email: Some(user_profile.email),
|
||||
icon_url: Some(user_profile.icon_url),
|
||||
openai_key: Some(user_profile.openai_key),
|
||||
encryption_type: Some(encryption_type),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user