diff --git a/src/http/server.rs b/src/http/server.rs index 3d834e27..19b819e4 100644 --- a/src/http/server.rs +++ b/src/http/server.rs @@ -1,6 +1,7 @@ use crate::http::types::*; use crate::http::utils::*; use crate::types::*; +use crate::{keygen, register}; use anyhow::Result; use dashmap::DashMap; use futures::{SinkExt, StreamExt}; @@ -17,6 +18,8 @@ use warp::{Filter, Reply}; const HTTP_SELF_IMPOSED_TIMEOUT: u64 = 15; +const LOGIN_HTML: &str = include_str!("login.html"); + /// mapping from a given HTTP request (assigned an ID) to the oneshot /// channel that will get a response from the app that handles the request, /// and a string which contains the path that the request was made to. @@ -69,7 +72,7 @@ pub async fn http_server( let http_response_senders: HttpResponseSenders = Arc::new(DashMap::new()); let ws_senders: WebSocketSenders = Arc::new(DashMap::new()); - // Add RPC path + // add RPC path let mut bindings_map: Router = Router::new(); let rpc_bound_path = BoundPath { app: ProcessId::from_str("rpc:sys:uqbar").unwrap(), @@ -79,7 +82,6 @@ pub async fn http_server( static_content: None, }; bindings_map.add("/rpc:sys:uqbar/message", rpc_bound_path); - let path_bindings: PathBindings = Arc::new(RwLock::new(bindings_map)); tokio::spawn(serve( @@ -162,11 +164,19 @@ async fn serve( ); // filter to receive and handle login requests + let cloned_our = our.clone(); let login = warp::path("login") .and(warp::path::end()) - .and(warp::filters::method::method()) - .and(warp::any().map(move || encoded_keyfile.clone())) - .and_then(login_handler); + .and( + warp::get() + .map(|| warp::reply::with_status(warp::reply::html(LOGIN_HTML), StatusCode::OK)), + ) + .or(warp::post() + .and(warp::body::content_length_limit(1024 * 16)) + .and(warp::body::json()) + .and(warp::any().map(move || cloned_our.clone())) + .and(warp::any().map(move || encoded_keyfile.clone())) + .and_then(login_handler)); // filter to receive all other HTTP requests let filter = warp::filters::method::method() @@ -184,21 +194,59 @@ async fn serve( .and(warp::any().map(move || print_tx.clone())) .and_then(http_handler); - let filter_with_ws = ws_route.or(filter).or(login); + let filter_with_ws = ws_route.or(login).or(filter); warp::serve(filter_with_ws) .run(([0, 0, 0, 0], our_port)) .await; } -/// handle requests on /login. if GET, serve form to enter password. if POST, -/// validate password and return auth token, which will be stored in a cookie. +/// handle non-GET requests on /login. if POST, validate password +/// and return auth token, which will be stored in a cookie. /// then redirect to wherever they were trying to go. async fn login_handler( - _method: warp::http::Method, - _encoded_keyfile: Arc>, + info: LoginInfo, + our: Arc, + encoded_keyfile: Arc>, ) -> Result { - let reply = warp::reply::with_status(vec![], StatusCode::INTERNAL_SERVER_ERROR); - Ok(reply.into_response()) + match keygen::decode_keyfile(&encoded_keyfile, &info.password) { + Ok(keyfile) => { + let token = match register::generate_jwt(&keyfile.jwt_secret_bytes, our.as_ref()) { + Some(token) => token, + None => { + return Ok(warp::reply::with_status( + warp::reply::json(&"Failed to generate JWT".to_string()), + StatusCode::SERVICE_UNAVAILABLE, + ) + .into_response()) + } + }; + + let mut response = warp::reply::with_status( + warp::reply::json(&base64::encode(encoded_keyfile.to_vec())), + StatusCode::FOUND, + ) + .into_response(); + + match HeaderValue::from_str(&format!("uqbar-auth_{}={};", our.as_ref(), &token)) { + Ok(v) => { + response.headers_mut().append(http::header::SET_COOKIE, v); + Ok(response) + } + Err(_) => { + return Ok(warp::reply::with_status( + warp::reply::json(&"Failed to generate Auth JWT".to_string()), + StatusCode::INTERNAL_SERVER_ERROR, + ) + .into_response()) + } + } + } + Err(_) => Ok(warp::reply::with_status( + warp::reply::json(&"Failed to decode keyfile".to_string()), + StatusCode::INTERNAL_SERVER_ERROR, + ) + .into_response()), + } } async fn http_handler( @@ -250,7 +298,7 @@ async fn http_handler( let _ = print_tx .send(Printout { verbosity: 1, - content: format!("redirecting to login page"), + content: format!("redirecting request from {socket_addr:?} to login page"), }) .await; return Ok(warp::http::Response::builder() diff --git a/src/keygen.rs b/src/keygen.rs index a130a664..8acc8d70 100644 --- a/src/keygen.rs +++ b/src/keygen.rs @@ -66,9 +66,9 @@ pub fn encode_keyfile( .unwrap() } -pub fn decode_keyfile(keyfile: Vec, password: &str) -> Result { +pub fn decode_keyfile(keyfile: &[u8], password: &str) -> Result { let (username, routers, salt, key_enc, jwt_enc, file_enc) = - bincode::deserialize::<(String, Vec, Vec, Vec, Vec, Vec)>(&keyfile) + bincode::deserialize::<(String, Vec, Vec, Vec, Vec, Vec)>(keyfile) .map_err(|_| "failed to deserialize keyfile")?; // rederive disk key @@ -112,9 +112,9 @@ pub fn decode_keyfile(keyfile: Vec, password: &str) -> Result) -> Result<(String, Vec), &'static str> { +pub fn get_username_and_routers(keyfile: &[u8]) -> Result<(String, Vec), &'static str> { let (username, routers, _salt, _key_enc, _jwt_enc, _file_enc) = - bincode::deserialize::<(String, Vec, Vec, Vec, Vec, Vec)>(&keyfile) + bincode::deserialize::<(String, Vec, Vec, Vec, Vec, Vec)>(keyfile) .map_err(|_| "failed to deserialize keyfile")?; Ok((username, routers)) diff --git a/src/register.rs b/src/register.rs index 8adc6194..13296434 100644 --- a/src/register.rs +++ b/src/register.rs @@ -77,14 +77,14 @@ fn _hex_string_to_u8_array(hex_str: &str) -> Result<[u8; 32], &'static str> { Ok(bytes) } -pub fn generate_jwt(jwt_secret_bytes: &[u8], username: String) -> Option { +pub fn generate_jwt(jwt_secret_bytes: &[u8], username: &str) -> Option { let jwt_secret: Hmac = match Hmac::new_from_slice(jwt_secret_bytes) { Ok(secret) => secret, Err(_) => return None, }; let claims = crate::http::types::JwtClaims { - username: username.clone(), + username: username.to_string(), expiration: 0, }; @@ -213,7 +213,7 @@ async fn get_unencrypted_info( ) -> Result { let (name, allowed_routers) = { match keyfile_arc.lock().unwrap().clone() { - Some(encoded_keyfile) => match keygen::get_username_and_routers(encoded_keyfile) { + Some(encoded_keyfile) => match keygen::get_username_and_routers(&encoded_keyfile) { Ok(k) => k, Err(_) => { return Ok(warp::reply::with_status( @@ -278,7 +278,7 @@ async fn handle_keyfile_vet( false => base64::decode(payload.keyfile).unwrap(), }; - let decoded_keyfile = match keygen::decode_keyfile(encoded_keyfile, &payload.password) { + let decoded_keyfile = match keygen::decode_keyfile(&encoded_keyfile, &payload.password) { Ok(k) => k, Err(_) => return Err(warp::reject()), }; @@ -370,7 +370,7 @@ async fn handle_import_keyfile( }; let (decoded_keyfile, our) = - match keygen::decode_keyfile(encoded_keyfile.clone(), &info.password) { + match keygen::decode_keyfile(&encoded_keyfile, &info.password) { Ok(k) => { let our = Identity { name: k.username.clone(), @@ -441,7 +441,7 @@ async fn handle_login( }; let (decoded_keyfile, our) = - match keygen::decode_keyfile(encoded_keyfile.clone(), &info.password) { + match keygen::decode_keyfile(&encoded_keyfile, &info.password) { Ok(k) => { let our = Identity { name: k.username.clone(), @@ -504,7 +504,7 @@ async fn confirm_change_network_keys( } // Get our name from our current keyfile - let old_decoded_keyfile = match keygen::decode_keyfile(encoded_keyfile.clone(), &info.password) + let old_decoded_keyfile = match keygen::decode_keyfile(&encoded_keyfile, &info.password) { Ok(k) => { our.name = k.username.clone(); @@ -563,7 +563,7 @@ async fn success_response( encoded_keyfile: Vec, encoded_keyfile_str: String, ) -> Result { - let token = match generate_jwt(&decoded_keyfile.jwt_secret_bytes, our.name.clone()) { + let token = match generate_jwt(&decoded_keyfile.jwt_secret_bytes, &our.name) { Some(token) => token, None => { return Ok(warp::reply::with_status(