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 <henrik@affekt.org>
This commit is contained in:
16
README.md
16
README.md
@@ -62,6 +62,7 @@ You **must** have an existing premium Spotify subscription to use `ncspot`.
|
|||||||
- [Notification Formatting](#notification-formatting)
|
- [Notification Formatting](#notification-formatting)
|
||||||
- [Cover Drawing](#cover-drawing)
|
- [Cover Drawing](#cover-drawing)
|
||||||
- [Authentication](#authentication)
|
- [Authentication](#authentication)
|
||||||
|
- [Using a password manager](#using-a-password-manager)
|
||||||
|
|
||||||
## Resource Footprint Comparison
|
## 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
|
The `logout` command can be used to remove cached credentials. See
|
||||||
[Vim-Like Commands](#vim-like-commands).
|
[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.
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::process::Command;
|
||||||
|
|
||||||
use cursive::traits::Resizable;
|
use cursive::traits::Resizable;
|
||||||
use cursive::view::Nameable;
|
use cursive::view::Nameable;
|
||||||
use cursive::views::*;
|
use cursive::views::*;
|
||||||
@@ -60,6 +62,38 @@ pub fn create_credentials() -> Result<RespotCredentials, String> {
|
|||||||
.unwrap_or_else(|| Err("Didn't obtain any credentials".to_string()))
|
.unwrap_or_else(|| Err("Didn't obtain any credentials".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn credentials_eval(
|
||||||
|
username_cmd: &str,
|
||||||
|
password_cmd: &str,
|
||||||
|
) -> Result<RespotCredentials, String> {
|
||||||
|
fn eval(cmd: &str) -> Result<Vec<u8>, 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)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct AuthResponse {
|
pub struct AuthResponse {
|
||||||
pub credentials: RespotCredentials,
|
pub credentials: RespotCredentials,
|
||||||
|
|||||||
@@ -93,6 +93,13 @@ pub struct ConfigValues {
|
|||||||
pub statusbar_format: Option<String>,
|
pub statusbar_format: Option<String>,
|
||||||
pub library_tabs: Option<Vec<LibraryTab>>,
|
pub library_tabs: Option<Vec<LibraryTab>>,
|
||||||
pub hide_display_names: Option<bool>,
|
pub hide_display_names: Option<bool>,
|
||||||
|
pub credentials: Option<Credentials>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||||
|
pub struct Credentials {
|
||||||
|
pub username_cmd: Option<String>,
|
||||||
|
pub password_cmd: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||||
|
|||||||
12
src/main.rs
12
src/main.rs
@@ -191,7 +191,17 @@ fn main() -> Result<(), String> {
|
|||||||
info!("Using cached credentials");
|
info!("Using cached credentials");
|
||||||
c
|
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)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user