Credentials helper if config not present or unparseable

Fixes #1
This commit is contained in:
Michael Edwards
2019-03-27 22:35:31 +01:00
committed by Henrik Friedrichsen
parent 58f34b9288
commit d78e71871a
5 changed files with 248 additions and 26 deletions

169
src/authentication.rs Normal file
View File

@@ -0,0 +1,169 @@
use std::path::Path;
use cursive::view::Identifiable;
use cursive::views::*;
use cursive::{CbSink, Cursive};
use librespot::core::authentication::Credentials as RespotCredentials;
use librespot::protocol::authentication::AuthenticationType;
pub fn create_credentials(path: &Path) -> Result<RespotCredentials, String> {
let mut login_cursive = Cursive::default();
let mut info_buf = TextContent::new("Failed to authenticate\n");
info_buf.append(format!(
"Cannot read config file from {}\n",
path.to_str().unwrap()
));
let info_view = Dialog::around(TextView::new_with_content(info_buf.clone()))
.button("Login", move |s| {
let login_view = Dialog::new()
.title("Login with Spotify username and password")
.content(
ListView::new()
.child("username", EditView::new().with_id("spotify_user"))
.child(
"password",
EditView::new().secret().with_id("spotify_password"),
),
)
.button("Login", |s| {
let username = s
.call_on_id("spotify_user", |view: &mut EditView| view.get_content())
.unwrap()
.to_string();
let auth_data = s
.call_on_id("spotify_password", |view: &mut EditView| view.get_content())
.unwrap()
.to_string()
.as_bytes()
.to_vec();
s.set_user_data::<Result<RespotCredentials, String>>(Ok(RespotCredentials {
username,
auth_type: AuthenticationType::AUTHENTICATION_USER_PASS,
auth_data,
}));
s.quit();
})
.button("Quit", |s| s.quit());
s.pop_layer();
s.add_layer(login_view);
})
.button("Login with Facebook", |s| {
let urls: std::collections::HashMap<String, String> =
reqwest::get("https://login2.spotify.com/v1/config")
.expect("didn't connect")
.json()
.expect("didn't parse");
// not a dialog to let people copy & paste the URL
let url_notice = TextView::new(format!("Browse to {}", urls.get("login_url").unwrap()));
let controls = Button::new("Quit", |s| s.quit());
let login_view = LinearLayout::new(cursive::direction::Orientation::Vertical)
.child(url_notice)
.child(controls);
url_open(urls.get("login_url").unwrap().to_string());
crappy_poller(urls.get("credentials_url").unwrap(), &s.cb_sink());
s.pop_layer();
s.add_layer(login_view)
})
.button("Quit", |s| s.quit());
login_cursive.add_layer(info_view);
login_cursive.run();
login_cursive
.user_data()
.cloned()
.unwrap_or(Err("Didn't obtain any credentials".to_string()))
}
// TODO: better with futures?
fn crappy_poller(url: &str, app_sink: &CbSink) {
let app_sink = app_sink.clone();
let url = url.to_string();
std::thread::spawn(move || {
let timeout = std::time::Duration::from_secs(5 * 60);
let start_time = std::time::SystemTime::now();
while std::time::SystemTime::now()
.duration_since(start_time)
.unwrap_or(timeout)
< timeout
{
if let Ok(mut response) = reqwest::get(&url) {
if response.status() != reqwest::StatusCode::ACCEPTED {
let result = match response.status() {
reqwest::StatusCode::OK => {
let creds = response
.json::<AuthResponse>()
.expect("Unable to parse")
.credentials;
Ok(creds)
}
_ => Err(format!(
"Facebook auth failed with code {}: {}",
response.status(),
response.text().unwrap()
)),
};
app_sink
.send(Box::new(|s: &mut Cursive| {
s.set_user_data(result);
s.quit();
}))
.unwrap();
return;
}
}
std::thread::sleep(std::time::Duration::from_millis(1000));
}
app_sink
.send(Box::new(|s: &mut Cursive| {
s.set_user_data::<Result<RespotCredentials, String>>(Err(
"Timed out authenticating".to_string(),
));
s.quit();
}))
.unwrap();
});
}
#[derive(Serialize, Deserialize, Debug)]
pub struct AuthResponse {
pub credentials: RespotCredentials,
pub error: Option<String>,
}
// Thanks to Marko Mijalkovic for this, but I don't want the url crate
#[cfg(target_os = "windows")]
pub fn url_open(url: String) {
extern crate shell32;
extern crate winapi;
use std::ffi::CString;
use std::ptr;
unsafe {
shell32::ShellExecuteA(
ptr::null_mut(),
CString::new("open").unwrap().as_ptr(),
CString::new(url.replace("\n", "%0A")).unwrap().as_ptr(),
ptr::null(),
ptr::null(),
winapi::SW_SHOWNORMAL,
);
}
}
#[cfg(target_os = "macos")]
pub fn url_open(url: String) {
let _ = std::process::Command::new("open").arg(url).output();
}
#[cfg(target_os = "linux")]
pub fn url_open(url: String) {
let _ = std::process::Command::new("xdg-open").arg(url).output();
}