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:
inemajo
2023-03-01 11:28:11 +01:00
committed by GitHub
parent 3a3d8ae8b4
commit c2e030c2f0
4 changed files with 68 additions and 1 deletions

View File

@@ -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.

View File

@@ -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<RespotCredentials, 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)]
pub struct AuthResponse {
pub credentials: RespotCredentials,

View File

@@ -93,6 +93,13 @@ pub struct ConfigValues {
pub statusbar_format: Option<String>,
pub library_tabs: Option<Vec<LibraryTab>>,
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)]

View File

@@ -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)?,
}
}
}
};