diff options
-rw-r--r-- | Cargo.lock | 63 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/client.rs | 18 | ||||
-rw-r--r-- | src/cmd/stream.rs | 12 | ||||
-rw-r--r-- | src/cmd/watch.rs | 13 | ||||
-rw-r--r-- | src/dirs.rs | 40 | ||||
-rw-r--r-- | src/error.rs | 6 | ||||
-rw-r--r-- | src/main.rs | 2 | ||||
-rw-r--r-- | src/oauth.rs | 68 | ||||
-rw-r--r-- | src/oauth/recurse_center.rs | 34 | ||||
-rw-r--r-- | src/prelude.rs | 1 | ||||
-rw-r--r-- | src/server.rs | 126 |
12 files changed, 331 insertions, 53 deletions
@@ -96,6 +96,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "blake2b_simd" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "block-buffer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -166,6 +176,11 @@ dependencies = [ ] [[package]] +name = "constant_time_eq" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "cookie" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -373,6 +388,26 @@ dependencies = [ ] [[package]] +name = "directories" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dirs-sys" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "doc-comment" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1152,6 +1187,17 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "redox_users" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "regex" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1209,6 +1255,16 @@ dependencies = [ ] [[package]] +name = "rust-argon2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "rustc-demangle" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1438,6 +1494,7 @@ dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossterm 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", + "directories 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1933,6 +1990,7 @@ dependencies = [ "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" "checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" "checksum bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8a606a02debe2813760609f57a64a2ffd27d9fdf5b2f133eaca0b248dd92cdd2" +"checksum blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5850aeee1552f495dd0250014cf64b82b7c8879a89d83b33bbdace2cc4f63182" "checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" "checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" @@ -1942,6 +2000,7 @@ dependencies = [ "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" "checksum cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" "checksum cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46750b3f362965f197996c4448e4a0935e791bf7d6631bfce9ee0af3d24c919c" "checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" @@ -1962,6 +2021,8 @@ dependencies = [ "checksum curl 0.4.25 (registry+https://github.com/rust-lang/crates.io-index)" = "06aa71e9208a54def20792d877bc663d6aae0732b9852e612c4a933177c31283" "checksum curl-sys 0.4.23 (registry+https://github.com/rust-lang/crates.io-index)" = "f71cd2dbddb49c744c1c9e0b96106f50a634e8759ec51bcd5399a578700a3ab3" "checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" +"checksum directories 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c" +"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" "checksum doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "923dea538cea0aa3025e8685b20d6ee21ef99c4f77e954a30febbaac5ec73a97" "checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" @@ -2051,10 +2112,12 @@ dependencies = [ "checksum ratelimit_meter 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a37d4f95369ef809d01448bdf8d82ef974e3b21f3698d87015a575bb26f27724" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" "checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum reqwest 0.9.22 (registry+https://github.com/rust-lang/crates.io-index)" = "2c2064233e442ce85c77231ebd67d9eca395207dec2127fe0bbedde4bd29a650" +"checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" @@ -8,6 +8,7 @@ edition = "2018" bytes = "0.4" clap = "2" crossterm = "0.11" +directories = "2" env_logger = "0.7" futures = "0.1" lazy_static = "1" diff --git a/src/client.rs b/src/client.rs index e351eec..59ed96d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -280,8 +280,7 @@ impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static> msg.log("recv"); match msg { - // XXX store the id and use it on future requests - crate::protocol::Message::OauthRequest { url, id: _id } => { + crate::protocol::Message::OauthRequest { url, id } => { let mut state = None; let parsed_url = url::Url::parse(&url).unwrap(); for (k, v) in parsed_url.query_pairs() { @@ -294,6 +293,7 @@ impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static> crate::component_future::Poll::DidWork, Some(self.wait_for_oauth_response( state.map(|s| s.to_string()), + &id, )?), )) } @@ -322,6 +322,7 @@ impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static> fn wait_for_oauth_response( &self, state: Option<String>, + id: &str, ) -> Result< Box< dyn futures::future::Future< @@ -336,6 +337,8 @@ impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static> ).unwrap(); } + let auth_name = self.auth.name().to_string(); + let id = id.to_string(); let addr = OAUTH_LISTEN_ADDRESS .parse() .context(crate::error::ParseAddr)?; @@ -390,6 +393,17 @@ impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static> lines.into_inner().into_inner(), )) }) + .and_then(move |(msg, sock)| { + let id_file = crate::dirs::Dirs::new() + .data_file(&format!("client-oauth-{}", auth_name)); + tokio::fs::File::create(id_file) + .context(crate::error::CreateFile) + .and_then(|file| { + tokio::io::write_all(file, id) + .context(crate::error::WriteFile) + }) + .map(|_| (msg, sock)) + }) .and_then(|(msg, sock)| { let response = r"HTTP/1.1 200 OK diff --git a/src/cmd/stream.rs b/src/cmd/stream.rs index 66ed68b..c1e1efa 100644 --- a/src/cmd/stream.rs +++ b/src/cmd/stream.rs @@ -1,4 +1,5 @@ use crate::prelude::*; +use std::io::Read as _; use tokio::io::AsyncWrite as _; pub fn cmd<'a, 'b>(app: clap::App<'a, 'b>) -> clap::App<'a, 'b> { @@ -30,7 +31,16 @@ pub fn cmd<'a, 'b>(app: clap::App<'a, 'b>) -> clap::App<'a, 'b> { pub fn run<'a>(matches: &clap::ArgMatches<'a>) -> super::Result<()> { let auth = if matches.is_present("login-recurse-center") { - crate::protocol::Auth::RecurseCenter { id: None } + let auth = crate::protocol::Auth::RecurseCenter { id: None }; + let id_file = crate::dirs::Dirs::new() + .data_file(&format!("client-oauth-{}", auth.name())); + let id = std::fs::File::open(id_file).ok().and_then(|mut file| { + let mut id = vec![]; + file.read_to_end(&mut id).ok().map(|_| { + std::string::String::from_utf8_lossy(&id).to_string() + }) + }); + crate::protocol::Auth::RecurseCenter { id } } else { let username = matches .value_of("login-plain") diff --git a/src/cmd/watch.rs b/src/cmd/watch.rs index a4e5efa..d400cfc 100644 --- a/src/cmd/watch.rs +++ b/src/cmd/watch.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use std::io::Write as _; +use std::io::{Read as _, Write as _}; pub fn cmd<'a, 'b>(app: clap::App<'a, 'b>) -> clap::App<'a, 'b> { app.about("Watch teleterm streams") @@ -23,7 +23,16 @@ pub fn cmd<'a, 'b>(app: clap::App<'a, 'b>) -> clap::App<'a, 'b> { pub fn run<'a>(matches: &clap::ArgMatches<'a>) -> super::Result<()> { let auth = if matches.is_present("login-recurse-center") { - crate::protocol::Auth::RecurseCenter { id: None } + let auth = crate::protocol::Auth::RecurseCenter { id: None }; + let id_file = crate::dirs::Dirs::new() + .data_file(&format!("client-oauth-{}", auth.name())); + let id = std::fs::File::open(id_file).ok().and_then(|mut file| { + let mut id = vec![]; + file.read_to_end(&mut id).ok().map(|_| { + std::string::String::from_utf8_lossy(&id).to_string() + }) + }); + crate::protocol::Auth::RecurseCenter { id } } else { let username = matches .value_of("login-plain") diff --git a/src/dirs.rs b/src/dirs.rs new file mode 100644 index 0000000..12a2f15 --- /dev/null +++ b/src/dirs.rs @@ -0,0 +1,40 @@ +use crate::prelude::*; + +pub struct Dirs { + project_dirs: directories::ProjectDirs, +} + +impl Dirs { + pub fn new() -> Self { + // TODO: fall back to /var, /etc, etc if we're running without $HOME + // set + Self { + project_dirs: directories::ProjectDirs::from("", "", "teleterm") + .expect("failed to find valid home directory"), + } + } + + pub fn create_all(&self) -> Result<()> { + std::fs::create_dir_all(self.cache_dir()) + .context(crate::error::CreateDir)?; + std::fs::create_dir_all(self.data_dir()) + .context(crate::error::CreateDir)?; + Ok(()) + } + + fn cache_dir(&self) -> &std::path::Path { + self.project_dirs.cache_dir() + } + + pub fn cache_file(&self, name: &str) -> std::path::PathBuf { + self.cache_dir().join(name) + } + + fn data_dir(&self) -> &std::path::Path { + self.project_dirs.data_dir() + } + + pub fn data_file(&self, name: &str) -> std::path::PathBuf { + self.data_dir().join(name) + } +} diff --git a/src/error.rs b/src/error.rs index 337c9f6..e03ffdf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,6 +22,12 @@ pub enum Error { #[snafu(display("failed to create tls connector: {}", source))] CreateConnector { source: native_tls::Error }, + #[snafu(display("failed to create directory: {}", source))] + CreateDir { source: std::io::Error }, + + #[snafu(display("failed to create file: {}", source))] + CreateFile { source: tokio::io::Error }, + #[snafu(display("eof"))] EOF, diff --git a/src/main.rs b/src/main.rs index 89124d9..edba700 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ mod async_stdin; mod client; mod cmd; mod component_future; +mod dirs; mod error; mod key_reader; mod oauth; @@ -25,6 +26,7 @@ mod ttyrec; mod util; fn main() { + dirs::Dirs::new().create_all().unwrap(); env_logger::from_env( env_logger::Env::default().default_filter_or("info"), ) diff --git a/src/oauth.rs b/src/oauth.rs index 8d1944a..e23c44e 100644 --- a/src/oauth.rs +++ b/src/oauth.rs @@ -1,9 +1,16 @@ use crate::prelude::*; +use oauth2::TokenResponse as _; pub mod recurse_center; pub trait Oauth { fn client(&self) -> &oauth2::basic::BasicClient; + fn user_id(&self) -> &str; + fn name(&self) -> &str; + fn token_cache_file(&self) -> std::path::PathBuf { + let name = format!("server-oauth-{}-{}", self.name(), self.user_id()); + crate::dirs::Dirs::new().cache_file(&name) + } fn generate_authorize_url(&self) -> String { let (auth_url, _) = self @@ -13,15 +20,12 @@ pub trait Oauth { auth_url.to_string() } - fn get_token( + fn get_access_token_from_auth_code( &self, code: &str, - ) -> Box< - dyn futures::future::Future< - Item = oauth2::basic::BasicTokenResponse, - Error = Error, - > + Send, - > { + ) -> Box<dyn futures::future::Future<Item = String, Error = Error> + Send> + { + let token_cache_file = self.token_cache_file(); let fut = self .client() .exchange_code(oauth2::AuthorizationCode::new(code.to_string())) @@ -29,16 +33,62 @@ pub trait Oauth { .map_err(|e| { let msg = stringify_oauth2_http_error(&e); Error::ExchangeCode { msg } + }) + .and_then(move |token| { + cache_refresh_token(token_cache_file, &token) + .map(move |_| token.access_token().secret().to_string()) }); Box::new(fut) } - fn get_username( + fn get_access_token_from_refresh_token( &self, - code: &str, + token: &str, + ) -> Box<dyn futures::future::Future<Item = String, Error = Error> + Send> + { + let token_cache_file = self.token_cache_file(); + let fut = self + .client() + .exchange_refresh_token(&oauth2::RefreshToken::new( + token.to_string(), + )) + .request_async(oauth2::reqwest::async_http_client) + .map_err(|e| { + let msg = stringify_oauth2_http_error(&e); + Error::ExchangeCode { msg } + }) + .and_then(move |token| { + cache_refresh_token(token_cache_file, &token) + .map(move |_| token.access_token().secret().to_string()) + }); + Box::new(fut) + } + + fn get_username_from_access_token( + self: Box<Self>, + token: &str, ) -> Box<dyn futures::future::Future<Item = String, Error = Error> + Send>; } +fn cache_refresh_token( + token_cache_file: std::path::PathBuf, + token: &oauth2::basic::BasicTokenResponse, +) -> Box<dyn futures::future::Future<Item = (), Error = Error> + Send> { + let token_data = format!( + "{}\n{}\n", + token.refresh_token().unwrap().secret(), + token.access_token().secret(), + ); + let fut = tokio::fs::File::create(token_cache_file) + .context(crate::error::CreateFile) + .and_then(|file| { + tokio::io::write_all(file, token_data) + .context(crate::error::WriteFile) + }) + .map(|_| ()); + Box::new(fut) +} + pub struct Config { client_id: String, client_secret: String, diff --git a/src/oauth/recurse_center.rs b/src/oauth/recurse_center.rs index 26aa306..1bd6de7 100644 --- a/src/oauth/recurse_center.rs +++ b/src/oauth/recurse_center.rs @@ -1,14 +1,15 @@ use crate::prelude::*; -use oauth2::TokenResponse as _; pub struct Oauth { client: oauth2::basic::BasicClient, + user_id: String, } impl Oauth { - pub fn new(config: super::Config) -> Self { + pub fn new(config: super::Config, user_id: &str) -> Self { Self { client: config.into_basic_client(), + user_id: user_id.to_string(), } } } @@ -18,21 +19,24 @@ impl super::Oauth for Oauth { &self.client } - fn get_username( - &self, - code: &str, + fn user_id(&self) -> &str { + &self.user_id + } + + fn name(&self) -> &str { + "recurse_center" + } + + fn get_username_from_access_token( + self: Box<Self>, + token: &str, ) -> Box<dyn futures::future::Future<Item = String, Error = Error> + Send> { - let fut = self - .get_token(code) - .and_then(|token| { - let access_token = token.access_token(); - reqwest::r#async::Client::new() - .get("https://www.recurse.com/api/v1/profiles/me") - .bearer_auth(access_token.secret()) - .send() - .context(crate::error::GetProfile) - }) + let fut = reqwest::r#async::Client::new() + .get("https://www.recurse.com/api/v1/profiles/me") + .bearer_auth(token) + .send() + .context(crate::error::GetProfile) .and_then(|mut res| res.json().context(crate::error::ParseJson)) .map(|user: User| user.name()); Box::new(fut) diff --git a/src/prelude.rs b/src/prelude.rs index 05ebbe3..1bd29b7 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -6,3 +6,4 @@ pub use snafu::futures01::FutureExt as _; pub use snafu::{OptionExt as _, ResultExt as _}; pub use crate::error::{Error, Result}; +pub use crate::oauth::Oauth as _; diff --git a/src/server.rs b/src/server.rs index 5b82f3c..90728dd 100644 --- a/src/server.rs +++ b/src/server.rs @@ -147,6 +147,25 @@ impl ConnectionState { } } + fn login_oauth( + &mut self, + term_type: &str, + size: &crate::term::Size, + username: &str, + ) { + if let Self::Accepted = self { + *self = Self::LoggedIn { + username: username.to_string(), + term_info: TerminalInfo { + term: term_type.to_string(), + size: size.clone(), + }, + }; + } else { + unreachable!() + } + } + fn login_oauth_start( &mut self, term_type: &str, @@ -340,7 +359,16 @@ impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static> auth: &crate::protocol::Auth, term_type: &str, size: crate::term::Size, - ) -> Result<()> { + ) -> Result< + Option< + Box< + dyn futures::future::Future< + Item = (ConnectionState, crate::protocol::Message), + Error = Error, + > + Send, + >, + >, + > { if size.rows >= 1000 || size.cols >= 1000 { return Err(Error::TermTooBig { size }); } @@ -359,7 +387,7 @@ impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static> )); } oauth if oauth.is_oauth() => { - let (id, client) = match oauth { + let (refresh, client) = match oauth { crate::protocol::Auth::RecurseCenter { id } => { // XXX this needs some kind of real configuration // system @@ -376,7 +404,7 @@ impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static> url::Url::parse(&redirect_url).unwrap(); ( - id, + id.is_some(), Box::new( crate::oauth::recurse_center::Oauth::new( crate::oauth::recurse_center::config( @@ -384,6 +412,9 @@ impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static> &client_secret, redirect_url, ), + &id.clone().unwrap_or_else(|| { + format!("{}", uuid::Uuid::new_v4()) + }), ), ), ) @@ -391,29 +422,64 @@ impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static> _ => unreachable!(), }; + conn.oauth_client = Some(client); + let client = conn.oauth_client.as_ref().unwrap(); + log::info!( "{}: login(oauth({}), {:?})", conn.id, auth.name(), - id + client.user_id() ); - conn.oauth_client = Some(client); - if let Some(_id) = id { - // refresh - unimplemented!() + if refresh { + let term_type = term_type.to_string(); + let client = conn.oauth_client.take().unwrap(); + let mut new_state = conn.state.clone(); + let fut = + tokio::fs::File::open(client.token_cache_file()) + .context(crate::error::OpenFile) + .and_then(|file| { + tokio::io::lines(std::io::BufReader::new( + file, + )) + .into_future() + .map_err(|(e, _)| e) + .context(crate::error::ReadFile) + }) + .and_then(|(refresh_token, _)| { + // XXX unwrap here isn't super safe + let refresh_token = refresh_token.unwrap(); + client + .get_access_token_from_refresh_token( + refresh_token.trim(), + ) + .and_then(|access_token| { + client.get_username_from_access_token( + &access_token, + ) + }) + }) + .map(move |username| { + new_state.login_oauth( + &term_type, &size, &username, + ); + ( + new_state, + crate::protocol::Message::logged_in( + &username, + ), + ) + }); + return Ok(Some(Box::new(fut))); } else { - let id = format!("{}", uuid::Uuid::new_v4()); - conn.state.login_oauth_start(term_type, &size); + let authorize_url = client.generate_authorize_url(); + let user_id = client.user_id().to_string(); conn.send_message( crate::protocol::Message::oauth_request( - &conn - .oauth_client - .as_ref() - .unwrap() - .generate_authorize_url(), - &id, + &authorize_url, + &user_id, ), ); } @@ -421,7 +487,7 @@ impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static> _ => unreachable!(), } - Ok(()) + Ok(None) } fn handle_message_start_streaming( @@ -532,17 +598,20 @@ impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static> >, >, > { - let client = conn.oauth_client.as_ref().ok_or_else(|| { + let client = conn.oauth_client.take().ok_or_else(|| { Error::UnexpectedMessage { message: crate::protocol::Message::oauth_response(code), } })?; let mut new_state = conn.state.clone(); - let fut = client.get_username(code).map(|username| { - new_state.login_oauth_finish(&username); - (new_state, crate::protocol::Message::logged_in(&username)) - }); + let fut = client + .get_access_token_from_auth_code(code) + .and_then(|token| client.get_username_from_access_token(&token)) + .map(|username| { + new_state.login_oauth_finish(&username); + (new_state, crate::protocol::Message::logged_in(&username)) + }); Ok(Some(Box::new(fut))) } @@ -551,7 +620,16 @@ impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static> &mut self, conn: &mut Connection<S>, message: crate::protocol::Message, - ) -> Result<()> { + ) -> Result< + Option< + Box< + dyn futures::future::Future< + Item = (ConnectionState, crate::protocol::Message), + Error = Error, + > + Send, + >, + >, + > { match message { crate::protocol::Message::Login { auth, @@ -691,7 +769,7 @@ impl<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static> match conn.state { ConnectionState::Accepted { .. } => { - self.handle_accepted_message(conn, message).map(|_| None) + self.handle_accepted_message(conn, message) } ConnectionState::LoggingIn { .. } => { self.handle_logging_in_message(conn, message) |