Add credentials_url setting for keychain storage (#50047)

Added support for `credentials_url`, falling back to `server_url`, to be
used as the key for storing information inside Keychain Access. This
allows two copies of Zed to be used side by side if the other is
launched with a custom `--user-data-dir`.

- [x] Added a solid test coverage and/or screenshots from doing manual
testing
- [x] Done a self-review taking into account security and performance
aspects

Release Notes:

- Added support for `credentials_url`, falling back to `server_url`, to
be used as the key for storing information inside Keychain Access.
This commit is contained in:
Philip Arndt 2026-04-23 20:58:14 +12:00 committed by GitHub
parent 8f684fcb95
commit 4b01676a3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 31 additions and 6 deletions

View file

@ -102,6 +102,15 @@ actions!(
#[derive(Deserialize, RegisterSetting)]
pub struct ClientSettings {
pub server_url: String,
/// Overrides the key used to store credentials in the system keychain.
/// Defaults to `server_url` when unset.
///
/// Useful when running multiple Zed instances side by side without them
/// overwriting each other's keychain entries.
///
/// Note: changing this after signing in will require signing in again, as
/// existing credentials are stored under the old key.
pub credentials_url: Option<String>,
}
impl Settings for ClientSettings {
@ -109,10 +118,12 @@ impl Settings for ClientSettings {
if let Some(server_url) = &*ZED_SERVER_URL {
return Self {
server_url: server_url.clone(),
credentials_url: content.credentials_url.clone(),
};
}
Self {
server_url: content.server_url.clone().unwrap(),
credentials_url: content.credentials_url.clone(),
}
}
}
@ -351,6 +362,12 @@ impl ClientCredentialsProvider {
Ok(cx.update(|cx| ClientSettings::get_global(cx).server_url.clone()))
}
/// Returns the key used for credential storage in the system keychain.
fn credentials_url(&self, cx: &AsyncApp) -> Result<String> {
let from_settings = cx.update(|cx| ClientSettings::get_global(cx).credentials_url.clone());
Ok(from_settings.unwrap_or(self.server_url(cx)?))
}
/// Reads the credentials from the provider.
fn read_credentials<'a>(
&'a self,
@ -361,10 +378,10 @@ impl ClientCredentialsProvider {
return None;
}
let server_url = self.server_url(cx).ok()?;
let credentials_url = self.credentials_url(cx).ok()?;
let (user_id, access_token) = self
.provider
.read_credentials(&server_url, cx)
.read_credentials(&credentials_url, cx)
.await
.log_err()
.flatten()?;
@ -385,10 +402,10 @@ impl ClientCredentialsProvider {
cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
async move {
let server_url = self.server_url(cx)?;
let credentials_url = self.credentials_url(cx)?;
self.provider
.write_credentials(
&server_url,
&credentials_url,
&user_id.to_string(),
access_token.as_bytes(),
cx,
@ -404,8 +421,8 @@ impl ClientCredentialsProvider {
cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
async move {
let server_url = self.server_url(cx)?;
self.provider.delete_credentials(&server_url, cx).await
let credentials_url = self.credentials_url(cx)?;
self.provider.delete_credentials(&credentials_url, cx).await
}
.boxed_local()
}

View file

@ -179,6 +179,7 @@ impl VsCodeSettings {
base_keymap: Some(BaseKeymapContent::VSCode),
calls: None,
collaboration_panel: None,
credentials_url: None,
debugger: None,
diagnostics: None,
editor: self.editor_settings_content(),

View file

@ -183,6 +183,13 @@ pub struct SettingsContent {
/// The URL of the Zed server to connect to.
pub server_url: Option<String>,
/// The URL used as the key for credential storage.
///
/// When set, credentials are stored under this URL instead of `server_url`.
/// This allows running multiple Zed instances side by side without them
/// overwriting each other's keychain entries.
pub credentials_url: Option<String>,
/// Configuration for session-related features
pub session: Option<SessionSettingsContent>,
/// Control what info is collected by Zed.