Add server editing and deletion
This commit is contained in:
@@ -1,7 +1,14 @@
|
||||
use std::{env, fs, path::PathBuf};
|
||||
use std::{
|
||||
env, fs,
|
||||
path::PathBuf,
|
||||
process,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use gotcha_gitea::{Client, Provider};
|
||||
use security_framework::passwords::{get_generic_password, set_generic_password};
|
||||
use security_framework::passwords::{
|
||||
delete_generic_password, get_generic_password, set_generic_password,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
RepositoryPane,
|
||||
@@ -40,6 +47,7 @@ pub fn validate_server(
|
||||
name: name.into(),
|
||||
url: url.into(),
|
||||
provider,
|
||||
credential_account: String::new(),
|
||||
token: token.into(),
|
||||
})
|
||||
}
|
||||
@@ -59,14 +67,19 @@ pub fn load_preferences() -> Result<Preferences, String> {
|
||||
if !matches!(preferences.pull_status.as_str(), "open" | "closed") {
|
||||
preferences.pull_status = open_status();
|
||||
}
|
||||
let mut credentials_migrated = false;
|
||||
for server in &mut preferences.servers {
|
||||
if server.credential_account.is_empty() {
|
||||
server.credential_account = format!("{}|{}", server.name, server.url);
|
||||
credentials_migrated = true;
|
||||
}
|
||||
server.token = String::from_utf8(
|
||||
get_generic_password("de.rfc1437.gotcha", &keychain_account(server))
|
||||
.map_err(|error| format!("Cannot read the token for {}: {error}", server.name))?,
|
||||
)
|
||||
.map_err(|_| format!("The token for {} is not valid text.", server.name))?;
|
||||
}
|
||||
if favorites_migrated {
|
||||
if favorites_migrated || credentials_migrated {
|
||||
save_preferences(&preferences)?;
|
||||
}
|
||||
Ok(preferences)
|
||||
@@ -111,13 +124,54 @@ pub fn save_server_token(server: &Server) -> Result<(), String> {
|
||||
.map_err(|error| format!("Cannot save the token for {}: {error}", server.name))
|
||||
}
|
||||
|
||||
pub fn delete_server_token(server: &Server) -> Result<(), String> {
|
||||
match delete_generic_password("de.rfc1437.gotcha", &keychain_account(server)) {
|
||||
Ok(()) => Ok(()),
|
||||
// A missing Keychain item must not make an otherwise valid profile undeletable.
|
||||
Err(error) if error.code() == -25300 => Ok(()), // errSecItemNotFound
|
||||
Err(error) => Err(format!(
|
||||
"Cannot delete the token for {}: {error}",
|
||||
server.name
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assign_server_credential_account(server: &mut Server, existing: &[Server]) {
|
||||
if !server.credential_account.is_empty() {
|
||||
return;
|
||||
}
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_nanos();
|
||||
let prefix = format!("server-{}-{timestamp}", process::id());
|
||||
server.credential_account = (0_u32..)
|
||||
.map(|suffix| {
|
||||
if suffix == 0 {
|
||||
prefix.clone()
|
||||
} else {
|
||||
format!("{prefix}-{suffix}")
|
||||
}
|
||||
})
|
||||
.find(|candidate| {
|
||||
existing
|
||||
.iter()
|
||||
.all(|saved| saved.credential_account != *candidate)
|
||||
})
|
||||
.expect("a unique Keychain account must exist");
|
||||
}
|
||||
|
||||
fn preferences_path() -> Result<PathBuf, String> {
|
||||
let home = env::var_os("HOME").ok_or("Cannot find the app data directory.")?;
|
||||
Ok(PathBuf::from(home).join("Library/Application Support/Gotcha/preferences.json"))
|
||||
}
|
||||
|
||||
fn keychain_account(server: &Server) -> String {
|
||||
format!("{}|{}", server.name, server.url)
|
||||
if server.credential_account.is_empty() {
|
||||
format!("{}|{}", server.name, server.url)
|
||||
} else {
|
||||
server.credential_account.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -182,6 +236,15 @@ mod tests {
|
||||
let legacy_server: Server =
|
||||
serde_json::from_str(r#"{"name":"Work","url":"https://gitea.example.com"}"#).unwrap();
|
||||
assert_eq!(legacy_server.provider, Provider::Gitea);
|
||||
assert!(legacy_server.credential_account.is_empty());
|
||||
assert_eq!(
|
||||
keychain_account(&legacy_server),
|
||||
"Work|https://gitea.example.com"
|
||||
);
|
||||
let mut new_server = legacy_server.clone();
|
||||
assign_server_credential_account(&mut new_server, &[]);
|
||||
assert!(new_server.credential_account.starts_with("server-"));
|
||||
assert_eq!(keychain_account(&new_server), new_server.credential_account);
|
||||
assert_eq!(
|
||||
crate::domain::AppearanceMode::from_index(1),
|
||||
Some(crate::domain::AppearanceMode::Light)
|
||||
|
||||
Reference in New Issue
Block a user