From c2e030c2f0b0594e785f4c995a922ddfe2b5edf4 Mon Sep 17 00:00:00 2001 From: inemajo Date: Wed, 1 Mar 2023 11:28:11 +0100 Subject: [PATCH] Allow executing program to retrieve credentials * config / authentication: permit to call external program for getting credentials You can add into your ncspot/config.toml like this: ``` creds_username = "mylogin" creds_passeval = "pass my_pass_path_to_spot_password" ``` Or using any password manager who send your password.to stdout. If a newline is detected it will be automatically removed * Move credential commands into separate structure Also add an option for a username command * Document credential commands --------- Co-authored-by: Henrik Friedrichsen --- README.md | 16 ++++++++++++++++ src/authentication.rs | 34 ++++++++++++++++++++++++++++++++++ src/config.rs | 7 +++++++ src/main.rs | 12 +++++++++++- 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index da83aad..aba6659 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ You **must** have an existing premium Spotify subscription to use `ncspot`. - [Notification Formatting](#notification-formatting) - [Cover Drawing](#cover-drawing) - [Authentication](#authentication) + - [Using a password manager](#using-a-password-manager) ## Resource Footprint Comparison @@ -608,3 +609,18 @@ The credentials are stored in `~/.cache/ncspot/librespot/credentials.json` The `logout` command can be used to remove cached credentials. See [Vim-Like Commands](#vim-like-commands). + +### Using a password manager + +If you would like ncspot to retrieve your login data from command results, +i.e. because you use a password manager like `pass`, you can add the following +configuration: + +```toml +[credentials] +username_cmd = "echo username" +password_cmd = "pass spotify.com/username" +``` + +Do note that this is only required for the initial login or when your credential +token has expired. diff --git a/src/authentication.rs b/src/authentication.rs index 1e7dce7..3121f77 100644 --- a/src/authentication.rs +++ b/src/authentication.rs @@ -1,3 +1,5 @@ +use std::process::Command; + use cursive::traits::Resizable; use cursive::view::Nameable; use cursive::views::*; @@ -60,6 +62,38 @@ pub fn create_credentials() -> Result { .unwrap_or_else(|| Err("Didn't obtain any credentials".to_string())) } +pub fn credentials_eval( + username_cmd: &str, + password_cmd: &str, +) -> Result { + fn eval(cmd: &str) -> Result, String> { + println!("Executing \"{}\"", cmd); + let mut result = Command::new("sh") + .args(["-c", cmd]) + .output() + .map_err(|e| e.to_string())? + .stdout; + if let Some(&last_byte) = result.last() { + if last_byte == 10 { + result.pop(); + } + } + + Ok(result) + } + + println!("Retrieving username"); + let username = String::from_utf8_lossy(&eval(username_cmd)?).into(); + println!("Retrieving password"); + let password = eval(password_cmd)?; + + Ok(RespotCredentials { + username, + auth_type: AuthenticationType::AUTHENTICATION_USER_PASS, + auth_data: password, + }) +} + #[derive(Serialize, Deserialize, Debug)] pub struct AuthResponse { pub credentials: RespotCredentials, diff --git a/src/config.rs b/src/config.rs index 4dec449..a7ac036 100644 --- a/src/config.rs +++ b/src/config.rs @@ -93,6 +93,13 @@ pub struct ConfigValues { pub statusbar_format: Option, pub library_tabs: Option>, pub hide_display_names: Option, + pub credentials: Option, +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct Credentials { + pub username_cmd: Option, + pub password_cmd: Option, } #[derive(Serialize, Deserialize, Debug, Default, Clone)] diff --git a/src/main.rs b/src/main.rs index f76d907..59bb854 100644 --- a/src/main.rs +++ b/src/main.rs @@ -191,7 +191,17 @@ fn main() -> Result<(), String> { info!("Using cached credentials"); c } - None => credentials_prompt(None)?, + None => { + info!("Attempting to resolve credentials via username/password commands"); + let creds = cfg.values().credentials.clone().unwrap_or_default(); + + match (creds.username_cmd, creds.password_cmd) { + (Some(username_cmd), Some(password_cmd)) => { + authentication::credentials_eval(&username_cmd, &password_cmd)? + } + _ => credentials_prompt(None)?, + } + } } };