diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/bin/ngit/sub_commands/whoami.rs | 77 | ||||
| -rw-r--r-- | src/lib/git/mod.rs | 16 | ||||
| -rw-r--r-- | src/lib/login/existing.rs | 35 | ||||
| -rw-r--r-- | src/lib/login/mod.rs | 2 |
4 files changed, 100 insertions, 30 deletions
diff --git a/src/bin/ngit/sub_commands/whoami.rs b/src/bin/ngit/sub_commands/whoami.rs index 19ce573..5c0a461 100644 --- a/src/bin/ngit/sub_commands/whoami.rs +++ b/src/bin/ngit/sub_commands/whoami.rs | |||
| @@ -41,8 +41,10 @@ struct WhoamiJson { | |||
| 41 | local: Option<UserJson>, | 41 | local: Option<UserJson>, |
| 42 | #[serde(skip_serializing_if = "Option::is_none")] | 42 | #[serde(skip_serializing_if = "Option::is_none")] |
| 43 | global: Option<UserJson>, | 43 | global: Option<UserJson>, |
| 44 | #[serde(skip_serializing_if = "Option::is_none")] | ||
| 45 | system: Option<UserJson>, | ||
| 44 | /// The account that would be used for operations in the current context | 46 | /// The account that would be used for operations in the current context |
| 45 | /// (local takes priority over global). | 47 | /// (local > global > system, matching git's priority order). |
| 46 | #[serde(skip_serializing_if = "Option::is_none")] | 48 | #[serde(skip_serializing_if = "Option::is_none")] |
| 47 | active: Option<UserJson>, | 49 | active: Option<UserJson>, |
| 48 | } | 50 | } |
| @@ -62,7 +64,7 @@ pub async fn launch(args: &Cli, command_args: &SubCommandArgs) -> Result<()> { | |||
| 62 | 64 | ||
| 63 | let signer_info = extract_signer_cli_arguments(args).unwrap_or(None); | 65 | let signer_info = extract_signer_cli_arguments(args).unwrap_or(None); |
| 64 | 66 | ||
| 65 | // Try to load local login (silent, no prompts) | 67 | // Try to load login from each config level (silent, no prompts) |
| 66 | let local = load_user_for_scope( | 68 | let local = load_user_for_scope( |
| 67 | git_repo.as_ref(), | 69 | git_repo.as_ref(), |
| 68 | signer_info.as_ref(), | 70 | signer_info.as_ref(), |
| @@ -71,7 +73,6 @@ pub async fn launch(args: &Cli, command_args: &SubCommandArgs) -> Result<()> { | |||
| 71 | ) | 73 | ) |
| 72 | .await; | 74 | .await; |
| 73 | 75 | ||
| 74 | // Try to load global login (silent, no prompts) | ||
| 75 | let global = load_user_for_scope( | 76 | let global = load_user_for_scope( |
| 76 | git_repo.as_ref(), | 77 | git_repo.as_ref(), |
| 77 | signer_info.as_ref(), | 78 | signer_info.as_ref(), |
| @@ -80,45 +81,67 @@ pub async fn launch(args: &Cli, command_args: &SubCommandArgs) -> Result<()> { | |||
| 80 | ) | 81 | ) |
| 81 | .await; | 82 | .await; |
| 82 | 83 | ||
| 84 | let system = load_user_for_scope( | ||
| 85 | git_repo.as_ref(), | ||
| 86 | signer_info.as_ref(), | ||
| 87 | client.as_ref(), | ||
| 88 | SignerInfoSource::GitSystem, | ||
| 89 | ) | ||
| 90 | .await; | ||
| 91 | |||
| 83 | if let Some(client) = client { | 92 | if let Some(client) = client { |
| 84 | client.disconnect().await?; | 93 | client.disconnect().await?; |
| 85 | } | 94 | } |
| 86 | 95 | ||
| 96 | // Active account follows git's priority order: local > global > system | ||
| 97 | let active_scope = if local.is_some() { | ||
| 98 | Some("local") | ||
| 99 | } else if global.is_some() { | ||
| 100 | Some("global") | ||
| 101 | } else if system.is_some() { | ||
| 102 | Some("system") | ||
| 103 | } else { | ||
| 104 | None | ||
| 105 | }; | ||
| 106 | |||
| 87 | if command_args.json { | 107 | if command_args.json { |
| 88 | // active = local if present, else global | 108 | let active = active_scope.and_then(|scope| match scope { |
| 89 | let active = local | 109 | "local" => local.as_ref().map(|u| make_user_json(u, scope)), |
| 90 | .as_ref() | 110 | "global" => global.as_ref().map(|u| make_user_json(u, scope)), |
| 91 | .map(|u| make_user_json(u, "local")) | 111 | "system" => system.as_ref().map(|u| make_user_json(u, scope)), |
| 92 | .or_else(|| global.as_ref().map(|u| make_user_json(u, "global"))); | 112 | _ => None, |
| 113 | }); | ||
| 93 | 114 | ||
| 94 | let output = WhoamiJson { | 115 | let output = WhoamiJson { |
| 95 | local: local.as_ref().map(|u| make_user_json(u, "local")), | 116 | local: local.as_ref().map(|u| make_user_json(u, "local")), |
| 96 | global: global.as_ref().map(|u| make_user_json(u, "global")), | 117 | global: global.as_ref().map(|u| make_user_json(u, "global")), |
| 118 | system: system.as_ref().map(|u| make_user_json(u, "system")), | ||
| 97 | active, | 119 | active, |
| 98 | }; | 120 | }; |
| 99 | println!("{}", serde_json::to_string_pretty(&output)?); | 121 | println!("{}", serde_json::to_string_pretty(&output)?); |
| 122 | } else if local.is_none() && global.is_none() && system.is_none() { | ||
| 123 | println!("not logged in"); | ||
| 124 | println!(); | ||
| 125 | println!("use `ngit account login` to log in"); | ||
| 100 | } else { | 126 | } else { |
| 101 | match (local.as_ref(), global.as_ref()) { | 127 | type UserEntry = Option<(String, String, Option<String>)>; |
| 102 | (None, None) => { | 128 | let entries: &[(&str, &UserEntry)] = |
| 103 | println!("not logged in"); | 129 | &[("local", &local), ("global", &global), ("system", &system)]; |
| 104 | println!(); | 130 | let mut first = true; |
| 105 | println!("use `ngit account login` to log in"); | 131 | for (scope, user) in entries { |
| 106 | } | 132 | if let Some(u) = user { |
| 107 | (Some(u), None) => { | 133 | if !first { |
| 108 | println!("logged in to local repository as:"); | 134 | println!(); |
| 135 | } | ||
| 136 | first = false; | ||
| 137 | let is_active = active_scope == Some(scope); | ||
| 138 | if is_active { | ||
| 139 | println!("{scope} (active):"); | ||
| 140 | } else { | ||
| 141 | println!("{scope}:"); | ||
| 142 | } | ||
| 109 | print_user_human(u); | 143 | print_user_human(u); |
| 110 | } | 144 | } |
| 111 | (None, Some(u)) => { | ||
| 112 | println!("logged in globally as:"); | ||
| 113 | print_user_human(u); | ||
| 114 | } | ||
| 115 | (Some(local_u), Some(global_u)) => { | ||
| 116 | println!("local (active):"); | ||
| 117 | print_user_human(local_u); | ||
| 118 | println!(); | ||
| 119 | println!("global:"); | ||
| 120 | print_user_human(global_u); | ||
| 121 | } | ||
| 122 | } | 145 | } |
| 123 | } | 146 | } |
| 124 | 147 | ||
diff --git a/src/lib/git/mod.rs b/src/lib/git/mod.rs index 23fa5f0..ca7aa3f 100644 --- a/src/lib/git/mod.rs +++ b/src/lib/git/mod.rs | |||
| @@ -1089,6 +1089,22 @@ pub fn get_git_config_item(git_repo: &Option<&Repo>, item: &str) -> Result<Optio | |||
| 1089 | } | 1089 | } |
| 1090 | } | 1090 | } |
| 1091 | 1091 | ||
| 1092 | /// Read a config item from the system-level git config only (e.g. | ||
| 1093 | /// /etc/gitconfig). | ||
| 1094 | pub fn get_git_config_item_system(item: &str) -> Result<Option<String>> { | ||
| 1095 | let config = git2::Config::open_default().context("failed to open git config")?; | ||
| 1096 | // Try system level first, then ProgramData (Windows equivalent) | ||
| 1097 | for level in [git2::ConfigLevel::System, git2::ConfigLevel::ProgramData] { | ||
| 1098 | if let Ok(level_config) = config.open_level(level) { | ||
| 1099 | match level_config.get_entry(item) { | ||
| 1100 | Ok(entry) => return Ok(entry.value().map(|v| v.to_string())), | ||
| 1101 | Err(_) => continue, | ||
| 1102 | } | ||
| 1103 | } | ||
| 1104 | } | ||
| 1105 | Ok(None) | ||
| 1106 | } | ||
| 1107 | |||
| 1092 | pub fn save_git_config_item(git_repo: &Option<&Repo>, item: &str, value: &str) -> Result<()> { | 1108 | pub fn save_git_config_item(git_repo: &Option<&Repo>, item: &str, value: &str) -> Result<()> { |
| 1093 | if let Some(git_repo) = git_repo { | 1109 | if let Some(git_repo) = git_repo { |
| 1094 | git_repo.save_git_config_item(item, value, false) | 1110 | git_repo.save_git_config_item(item, value, false) |
diff --git a/src/lib/login/existing.rs b/src/lib/login/existing.rs index e60621d..2e45ca4 100644 --- a/src/lib/login/existing.rs +++ b/src/lib/login/existing.rs | |||
| @@ -18,7 +18,7 @@ use crate::client::MockConnect; | |||
| 18 | use crate::{ | 18 | use crate::{ |
| 19 | cli_interactor::{Interactor, InteractorPrompt, PromptPasswordParms}, | 19 | cli_interactor::{Interactor, InteractorPrompt, PromptPasswordParms}, |
| 20 | client::fetch_public_key, | 20 | client::fetch_public_key, |
| 21 | git::{Repo, RepoActions, get_git_config_item}, | 21 | git::{Repo, RepoActions, get_git_config_item, get_git_config_item_system}, |
| 22 | }; | 22 | }; |
| 23 | 23 | ||
| 24 | /// load signer from git config and UserProfile from cache or relays | 24 | /// load signer from git config and UserProfile from cache or relays |
| @@ -62,7 +62,8 @@ pub async fn load_existing_login( | |||
| 62 | Ok((signer, user_ref, source)) | 62 | Ok((signer, user_ref, source)) |
| 63 | } | 63 | } |
| 64 | 64 | ||
| 65 | /// priority order: cli arguments, local git config, global git config | 65 | /// priority order: cli arguments, local git config, global git config, system |
| 66 | /// git config | ||
| 66 | pub fn get_signer_info( | 67 | pub fn get_signer_info( |
| 67 | git_repo: &Option<&Repo>, | 68 | git_repo: &Option<&Repo>, |
| 68 | signer_info: &Option<SignerInfo>, | 69 | signer_info: &Option<SignerInfo>, |
| @@ -82,6 +83,7 @@ pub fn get_signer_info( | |||
| 82 | SignerInfoSource::CommandLineArguments, | 83 | SignerInfoSource::CommandLineArguments, |
| 83 | SignerInfoSource::GitLocal, | 84 | SignerInfoSource::GitLocal, |
| 84 | SignerInfoSource::GitGlobal, | 85 | SignerInfoSource::GitGlobal, |
| 86 | SignerInfoSource::GitSystem, | ||
| 85 | ] | 87 | ] |
| 86 | } { | 88 | } { |
| 87 | if let Ok(res) = | 89 | if let Ok(res) = |
| @@ -91,7 +93,7 @@ pub fn get_signer_info( | |||
| 91 | break; | 93 | break; |
| 92 | } | 94 | } |
| 93 | } | 95 | } |
| 94 | result.context("failed to get or find signer info in cli arguments, local git config or global git config")? | 96 | result.context("failed to get or find signer info in cli arguments, local git config, global git config or system git config")? |
| 95 | } | 97 | } |
| 96 | Some(SignerInfoSource::CommandLineArguments) => { | 98 | Some(SignerInfoSource::CommandLineArguments) => { |
| 97 | if let Some(signer_info) = signer_info { | 99 | if let Some(signer_info) = signer_info { |
| @@ -158,6 +160,33 @@ pub fn get_signer_info( | |||
| 158 | bail!("no signer info in global git config") | 160 | bail!("no signer info in global git config") |
| 159 | } | 161 | } |
| 160 | } | 162 | } |
| 163 | Some(SignerInfoSource::GitSystem) => { | ||
| 164 | if let Some(nsec) = get_git_config_item_system("nostr.nsec") | ||
| 165 | .context("failed to get system git config")? | ||
| 166 | { | ||
| 167 | ( | ||
| 168 | SignerInfo::Nsec { | ||
| 169 | nsec: nsec.to_string(), | ||
| 170 | password: password.clone(), | ||
| 171 | npub: get_git_config_item_system("nostr.npub") | ||
| 172 | .context("failed to get system git config")?, | ||
| 173 | }, | ||
| 174 | SignerInfoSource::GitSystem, | ||
| 175 | ) | ||
| 176 | } else if let Some(bunker_uri) = get_git_config_item_system("nostr.bunker-uri") | ||
| 177 | .context("failed to get system git config")? | ||
| 178 | { | ||
| 179 | (SignerInfo::Bunker { | ||
| 180 | bunker_uri, bunker_app_key: get_git_config_item_system("nostr.bunker-app-key") | ||
| 181 | .context("failed to get system git config")? | ||
| 182 | .context("system git config item nostr.bunker-uri exists but nostr.bunker-app-key doesn't")?, | ||
| 183 | npub: get_git_config_item_system("nostr.npub") | ||
| 184 | .context("failed to get system git config")?, | ||
| 185 | }, SignerInfoSource::GitSystem) | ||
| 186 | } else { | ||
| 187 | bail!("no signer info in system git config") | ||
| 188 | } | ||
| 189 | } | ||
| 161 | }) | 190 | }) |
| 162 | } | 191 | } |
| 163 | 192 | ||
diff --git a/src/lib/login/mod.rs b/src/lib/login/mod.rs index 47847c3..938cec4 100644 --- a/src/lib/login/mod.rs +++ b/src/lib/login/mod.rs | |||
| @@ -65,6 +65,7 @@ pub enum SignerInfo { | |||
| 65 | pub enum SignerInfoSource { | 65 | pub enum SignerInfoSource { |
| 66 | GitLocal, | 66 | GitLocal, |
| 67 | GitGlobal, | 67 | GitGlobal, |
| 68 | GitSystem, | ||
| 68 | CommandLineArguments, | 69 | CommandLineArguments, |
| 69 | } | 70 | } |
| 70 | 71 | ||
| @@ -91,6 +92,7 @@ fn print_logged_in_as( | |||
| 91 | SignerInfoSource::CommandLineArguments => " via cli arguments", | 92 | SignerInfoSource::CommandLineArguments => " via cli arguments", |
| 92 | SignerInfoSource::GitLocal => " to local repository", | 93 | SignerInfoSource::GitLocal => " to local repository", |
| 93 | SignerInfoSource::GitGlobal => "", | 94 | SignerInfoSource::GitGlobal => "", |
| 95 | SignerInfoSource::GitSystem => " via system git config", | ||
| 94 | } | 96 | } |
| 95 | ); | 97 | ); |
| 96 | Ok(()) | 98 | Ok(()) |