fix(sync): bind browser data to one account owner
This commit is contained in:
@@ -11,8 +11,8 @@ use std::path::Path;
|
||||
|
||||
use ely_domain::ProfileId;
|
||||
use ely_sync_client::{
|
||||
ApiClientConfig, BearerToken, BearerTokenStore, SyncApiClient, SyncClientError, send_email_otp,
|
||||
verify_email_otp,
|
||||
ApiClientConfig, BearerToken, BearerTokenStore, SyncApiClient, SyncClientError, SyncOwnerStore,
|
||||
send_email_otp, verify_email_otp,
|
||||
};
|
||||
use gpui::Context;
|
||||
|
||||
@@ -386,7 +386,21 @@ fn spawn_verify_otp(profile_id: ProfileId, email: String, otp: String, tx: SyncS
|
||||
return;
|
||||
}
|
||||
};
|
||||
let update = SyncStateUpdate::AuthVerified { profile_id, email, token };
|
||||
let user_id = match SyncApiClient::new(config, token.clone())
|
||||
.and_then(|client| client.authenticated_user_id())
|
||||
{
|
||||
Ok(user_id) => user_id,
|
||||
Err(error) => {
|
||||
retire_bearer(token);
|
||||
let _ = tx.send(SyncStateUpdate::AuthError {
|
||||
profile_id,
|
||||
email,
|
||||
message: error.to_string(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
let update = SyncStateUpdate::AuthVerified { profile_id, email, user_id, token };
|
||||
if let Err(error) = tx.send(update) {
|
||||
retire_stale_auth_update(error.0.update);
|
||||
}
|
||||
@@ -407,18 +421,20 @@ pub(super) fn retire_stale_auth_update(update: SyncStateUpdate) {
|
||||
};
|
||||
std::thread::Builder::new()
|
||||
.name("ely-sync-auth-retire".to_string())
|
||||
.spawn(move || {
|
||||
let client = SyncApiClient::new(ApiClientConfig::production(), token);
|
||||
if let Ok(client) = client {
|
||||
let _ = client.sign_out();
|
||||
}
|
||||
})
|
||||
.spawn(move || retire_bearer(token))
|
||||
.map(|_| ())
|
||||
.unwrap_or_else(|error| {
|
||||
tracing::warn!(target: "ely::sync", error = %error, "spawn auth retire failed");
|
||||
});
|
||||
}
|
||||
|
||||
fn retire_bearer(token: BearerToken) {
|
||||
let client = SyncApiClient::new(ApiClientConfig::production(), token);
|
||||
if let Ok(client) = client {
|
||||
let _ = client.sign_out();
|
||||
}
|
||||
}
|
||||
|
||||
fn verified_bearer_from(update: SyncStateUpdate) -> Option<BearerToken> {
|
||||
match update {
|
||||
SyncStateUpdate::AuthVerified { token, .. } => Some(token),
|
||||
@@ -428,17 +444,28 @@ fn verified_bearer_from(update: SyncStateUpdate) -> Option<BearerToken> {
|
||||
|
||||
pub(super) fn save_verified_bearer(
|
||||
profile_id: &ProfileId,
|
||||
user_id: &str,
|
||||
token: &BearerToken,
|
||||
default_profile_id: Option<&ProfileId>,
|
||||
) -> Result<(), SyncClientError> {
|
||||
let profile_root = default_profile_data_root().ok_or_else(|| {
|
||||
SyncClientError::BearerCredentialStorage("profile data root is unavailable".to_string())
|
||||
})?;
|
||||
SyncOwnerStore::new(&profile_root).claim(user_id)?;
|
||||
let profile_dir = sync_profile_data_dir(&profile_root, profile_id);
|
||||
bearer_store_for_profile(profile_id, &profile_dir, &profile_root, default_profile_id)
|
||||
.save(token)
|
||||
}
|
||||
|
||||
pub(super) fn verified_session_persistence_message(error: &SyncClientError) -> String {
|
||||
match error {
|
||||
SyncClientError::SyncOwnerMismatch
|
||||
| SyncClientError::SyncOwnerUnclaimed
|
||||
| SyncClientError::SyncOwnerStorage(_) => error.to_string(),
|
||||
_ => "System credential access failed.".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "auth_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -4,7 +4,7 @@ use ely_domain::ProfileId;
|
||||
use super::super::{ShellState, sync_state::SyncStateUpdate};
|
||||
use super::{
|
||||
AuthFlowPhase, active_profile_sync_context_for, bearer_store_for_profile, normalize_email,
|
||||
verified_bearer_from,
|
||||
verified_bearer_from, verified_session_persistence_message,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -47,6 +47,7 @@ fn stale_verified_updates_retain_the_token_for_server_retirement()
|
||||
let update = SyncStateUpdate::AuthVerified {
|
||||
profile_id,
|
||||
email: "user@example.com".to_string(),
|
||||
user_id: "user-01".to_string(),
|
||||
token: token.clone(),
|
||||
};
|
||||
|
||||
@@ -54,6 +55,14 @@ fn stale_verified_updates_retain_the_token_for_server_retirement()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn owner_mismatch_is_actionable_in_the_account_form() {
|
||||
assert_eq!(
|
||||
verified_session_persistence_message(&ely_sync_client::SyncClientError::SyncOwnerMismatch),
|
||||
"This browser data belongs to a different Ely account"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_profile_has_no_sync_auth_context() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let state =
|
||||
|
||||
@@ -45,7 +45,7 @@ pub(crate) enum SyncStateUpdate {
|
||||
SignOutSucceeded { profile_id: ProfileId },
|
||||
SignOutFailed { profile_id: ProfileId, message: String },
|
||||
AuthOtpSent { profile_id: ProfileId, email: String },
|
||||
AuthVerified { profile_id: ProfileId, email: String, token: BearerToken },
|
||||
AuthVerified { profile_id: ProfileId, email: String, user_id: String, token: BearerToken },
|
||||
AuthError { profile_id: ProfileId, email: String, message: String },
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ impl ElyShell {
|
||||
self.release_auth_flow_barrier();
|
||||
}
|
||||
}
|
||||
SyncStateUpdate::AuthVerified { profile_id, email, token } => {
|
||||
SyncStateUpdate::AuthVerified { profile_id, email, user_id, token } => {
|
||||
let attempt_matches = active_profile_id(&self.state).as_ref()
|
||||
== Some(&profile_id)
|
||||
&& matches!(
|
||||
@@ -336,12 +336,14 @@ impl ElyShell {
|
||||
auth::retire_stale_auth_update(SyncStateUpdate::AuthVerified {
|
||||
profile_id,
|
||||
email,
|
||||
user_id,
|
||||
token,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
match auth::save_verified_bearer(
|
||||
&profile_id,
|
||||
&user_id,
|
||||
&token,
|
||||
self.default_profile_id.as_ref(),
|
||||
) {
|
||||
@@ -357,9 +359,10 @@ impl ElyShell {
|
||||
auth::retire_stale_auth_update(SyncStateUpdate::AuthVerified {
|
||||
profile_id: profile_id.clone(),
|
||||
email: email.clone(),
|
||||
user_id,
|
||||
token,
|
||||
});
|
||||
let message = "System credential access failed.".to_string();
|
||||
let message = auth::verified_session_persistence_message(&error);
|
||||
self.auth_flow_phase = auth::AuthFlowPhase::Error {
|
||||
profile_id,
|
||||
email,
|
||||
|
||||
Reference in New Issue
Block a user