diff --git a/crates/web-plugins/oob-messages/src/models.rs b/crates/web-plugins/oob-messages/src/models.rs index b41415a9..7733946d 100644 --- a/crates/web-plugins/oob-messages/src/models.rs +++ b/crates/web-plugins/oob-messages/src/models.rs @@ -39,13 +39,14 @@ use std::io::Result as IoResult; // ``` #[derive(Debug, Serialize, Deserialize, Clone)] -struct OobMessage { +pub(crate) struct OobMessage { #[serde(rename = "type")] oob_type: String, id: String, from: String, body: Body, } +type Ooburl = String; #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename = "use")] @@ -55,7 +56,11 @@ struct Body { label: String, accept: Vec, } - +impl Default for OobMessage { + fn default() -> Self { + OobMessage::new("") + } +} impl OobMessage { fn new(did: &str) -> Self { let id = uuid::Uuid::new_v4().to_string(); @@ -74,12 +79,14 @@ impl OobMessage { } } - fn serialize_oob_message(oob_message: &OobMessage, url: &str) -> Result { + fn serialize_oob_message(oob_message: &OobMessage, url: &str) -> Result { let plaintext = to_string(oob_message).map_err(|e| format!("Serialization error: {}", e))?; let encoded_jwm = Base64Url.encode(plaintext.as_bytes()); - Ok(format!("{}?_oob={}", url, encoded_jwm)) + let ooburl = format!("{}/_oob{}", url, encoded_jwm); + + Ok(ooburl) } } @@ -87,7 +94,6 @@ impl OobMessage { pub(crate) fn retrieve_or_generate_oob_inv( fs: &mut F, server_public_domain: &str, - server_local_port: &str, storage_dirpath: &str, ) -> Result where @@ -112,7 +118,7 @@ where let did = diddoc.id.clone(); let oob_message = OobMessage::new(&did); - let url: &String = &format!("{}:{}", server_public_domain, server_local_port); + let url: &String = &format!("{}", server_public_domain); let oob_url = OobMessage::serialize_oob_message(&oob_message, url) .map_err(|err| format!("Serialization error: {err}"))?; @@ -205,8 +211,10 @@ use std::path::Path; #[cfg(test)] mod tests { + use super::*; use mockall::{predicate::*, *}; + use multibase::Base::Base64Url; mock! { pub FileSystem {} @@ -218,6 +226,15 @@ mod tests { fn write_with_lock(&self, path: &Path, content: &str) -> IoResult<()>; } } + fn test_serialize_oobmessage() -> String { + let did = "did:key:z6MkfyTREjTxQ8hUwSwBPeDHf3uPL3qCjSSuNPwsyMpWUGH7#z6LSbuUXWSgPfpiDBjUK6E7yiCKMN2eKJsXn5b55ZgqGz6Mr"; + let oobmessage = OobMessage::new(did); + let server_public_domain = "https://example.com"; + + let url = format!("{}", server_public_domain); + let encoded_oob = OobMessage::serialize_oob_message(&oobmessage, &url).unwrap(); + encoded_oob + } #[test] fn test_create_oob_message() { @@ -249,15 +266,23 @@ mod tests { .unwrap_or_else(|err| panic!("Failed to serialize oob message: {}", err)); assert!(!oob_url.is_empty()); - assert!(oob_url.starts_with(&format!("{}?_oob=", url))); - assert!(oob_url.contains("_oob=")); + assert!(oob_url.starts_with(&format!("{}/_oob", url))); + assert!(oob_url.contains("_oob")); + } + #[test] + fn test_deserialize_oob_message() { + let did = "did:key:z6MkfyTREjTxQ8hUwSwBPeDHf3uPL3qCjSSuNPwsyMpWUGH7#z6LSbuUXWSgPfpiDBjUK6E7yiCKMN2eKJsXn5b55ZgqGz6Mr"; + let serialized_msg = test_serialize_oobmessage(); + let oobmessage: Vec<&str> = serialized_msg.split("/_oob").collect(); + let oobmessage = oobmessage.get(1).unwrap().to_string(); + let decode_msg = Base64Url.decode(oobmessage).unwrap(); + let oobmessage: OobMessage = serde_json::from_slice(&decode_msg).unwrap(); + assert_eq!(oobmessage.from, did); } - #[test] fn test_retrieve_or_generate_oob_inv() { // Test data let server_public_domain = "https://example.com"; - let server_local_port = "8080"; let storage_dirpath = "testpath"; let mut mock_fs = MockFileSystem::new(); @@ -295,12 +320,8 @@ mod tests { .withf(|path, _content| path == Path::new("testpath/oob_invitation.txt")) .returning(|_, _| Ok(())); - let result = retrieve_or_generate_oob_inv( - &mut mock_fs, - server_public_domain, - server_local_port, - storage_dirpath, - ); + let result = + retrieve_or_generate_oob_inv(&mut mock_fs, server_public_domain, storage_dirpath); assert!(result.is_ok()); } diff --git a/crates/web-plugins/oob-messages/src/plugin.rs b/crates/web-plugins/oob-messages/src/plugin.rs index 03f0c538..f283eb10 100644 --- a/crates/web-plugins/oob-messages/src/plugin.rs +++ b/crates/web-plugins/oob-messages/src/plugin.rs @@ -17,12 +17,12 @@ pub struct OOBMessages { struct OOBMessagesEnv { storage_dirpath: String, server_public_domain: String, - server_local_port: String, } #[derive(Clone)] pub(crate) struct OOBMessagesState { pub(crate) filesystem: Arc>, + pub(crate) oobmessage: String, } fn get_env() -> Result { @@ -33,14 +33,9 @@ fn get_env() -> Result { PluginError::InitError("SERVER_PUBLIC_DOMAIN env variable required".to_owned()) })?; - let server_local_port = std::env::var("SERVER_LOCAL_PORT").map_err(|_| { - PluginError::InitError("SERVER_LOCAL_PORT env variable required".to_owned()) - })?; - Ok(OOBMessagesEnv { storage_dirpath, server_public_domain, - server_local_port, }) } @@ -53,17 +48,13 @@ impl Plugin for OOBMessages { let env = get_env()?; let mut fs = StdFileSystem; - let oob_inv = retrieve_or_generate_oob_inv( - &mut fs, - &env.server_public_domain, - &env.server_local_port, - &env.storage_dirpath, - ) - .map_err(|err| { - PluginError::InitError(format!( - "Error retrieving or generating OOB invitation: {err}" - )) - })?; + let oob_inv = + retrieve_or_generate_oob_inv(&mut fs, &env.server_public_domain, &env.storage_dirpath) + .map_err(|err| { + PluginError::InitError(format!( + "Error retrieving or generating OOB invitation: {err}" + )) + })?; tracing::debug!("Out Of Band Invitation: {}", oob_inv); @@ -74,8 +65,12 @@ impl Plugin for OOBMessages { })?; self.env = Some(env); + let oobmessage: Vec<&str> = oob_inv.split("/_").collect(); + let oobmessage = oobmessage.get(1).unwrap_or(&"").to_string(); + self.state = Some(OOBMessagesState { filesystem: Arc::new(Mutex::new(fs)), + oobmessage, }); Ok(()) diff --git a/crates/web-plugins/oob-messages/src/web.rs b/crates/web-plugins/oob-messages/src/web.rs index a74e8916..0a78a269 100644 --- a/crates/web-plugins/oob-messages/src/web.rs +++ b/crates/web-plugins/oob-messages/src/web.rs @@ -4,18 +4,24 @@ use crate::{ web::handler::{handler_landing_page_oob, handler_oob_inv, handler_oob_qr}, }; use axum::{routing::get, Router}; +use handler::decode_oob_inv; use std::sync::Arc; pub(crate) fn routes(state: Arc) -> Router { + let invitation_path = format!("/_{}", &state.oobmessage); Router::new() // .route("/oob_url", get(handler_oob_inv)) .route("/oob_qr", get(handler_oob_qr)) .route("/", get(handler_landing_page_oob)) + // handle oob invitation to invitation message + .route(&invitation_path, get(decode_oob_inv)) .with_state(state) } #[cfg(test)] mod tests { + use crate::models::retrieve_or_generate_oob_inv; + use super::*; use axum::{ body::Body, @@ -30,10 +36,14 @@ mod tests { std::env::set_var("STORAGE_DIRPATH", "tmp"); std::env::set_var("SERVER_PUBLIC_DOMAIN", "example.com"); std::env::set_var("SERVER_LOCAL_PORT", "8080"); - - let fs = MockFileSystem; + let server_public_domain = std::env::var("SERVER_PUBLIC_DOMAIN").unwrap(); + let storage_dirpath = std::env::var("STORAGE_DIRPATH").unwrap(); + let mut fs = MockFileSystem; + let oobmessage = + retrieve_or_generate_oob_inv(&mut fs, &server_public_domain, &storage_dirpath).unwrap(); let state = Arc::new(OOBMessagesState { filesystem: Arc::new(Mutex::new(fs)), + oobmessage, }); let app = routes(state.clone()); diff --git a/crates/web-plugins/oob-messages/src/web/handler.rs b/crates/web-plugins/oob-messages/src/web/handler.rs index ef453595..65545a0b 100644 --- a/crates/web-plugins/oob-messages/src/web/handler.rs +++ b/crates/web-plugins/oob-messages/src/web/handler.rs @@ -1,41 +1,66 @@ +use std::{error::Error, sync::Arc}; + use super::OOBMessagesState; -use crate::models::{retrieve_or_generate_oob_inv, retrieve_or_generate_qr_image}; +use crate::models::{retrieve_or_generate_oob_inv, retrieve_or_generate_qr_image, OobMessage}; use axum::{ extract::State, http::{header, StatusCode}, response::{Html, IntoResponse, Response}, }; -use std::{error::Error, sync::Arc}; +use multibase::Base::Base64Url; +use serde_json::json; pub(crate) async fn handler_oob_inv(State(state): State>) -> Response { let mut fs = state.filesystem.lock().unwrap(); - let (server_public_domain, server_local_port, storage_dirpath) = - match get_environment_variables() { - Ok(result) => result, + let (server_public_domain, storage_dirpath) = match get_environment_variables() { + Ok(result) => result, + Err(err) => { + tracing::error!("Error: {err:?}"); + return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response(); + } + }; + + let html_content = + match retrieve_or_generate_oob_inv(&mut *fs, &server_public_domain, &storage_dirpath) { + Ok(oob_inv) => oob_inv, Err(err) => { - tracing::error!("Error: {err:?}"); - return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error") + tracing::error!("Failed to retrieve or generate oob invitation: {err:?}"); + return ( + StatusCode::SERVICE_UNAVAILABLE, + "Could not process request at this time. Please try again later", + ) .into_response(); } }; - let html_content = match retrieve_or_generate_oob_inv( - &mut *fs, - &server_public_domain, - &server_local_port, - &storage_dirpath, - ) { - Ok(oob_inv) => oob_inv, - Err(err) => { - tracing::error!("Failed to retrieve or generate oob invitation: {err:?}"); - return ( - StatusCode::SERVICE_UNAVAILABLE, - "Could not process request at this time. Please try again later", - ) - .into_response(); - } - }; + let html_content = format!( + r#" + + + + + + out of band invitation url + + + "#, + html_content + ); + Html(html_content).into_response() +} +pub(crate) async fn decode_oob_inv(State(state): State>) -> Response { + let encoded_inv = &state.oobmessage; + let encoded_inv: Vec<&str> = encoded_inv.split("oob").collect(); + let encoded_inv = encoded_inv.get(1).unwrap(); + let decoded_inv = Base64Url.decode(encoded_inv).unwrap_or_default(); + let oobmessage: OobMessage = serde_json::from_slice(&decoded_inv).unwrap_or_default(); + let view = json!(oobmessage); let html_content = format!( r#" @@ -53,41 +78,34 @@ pub(crate) async fn handler_oob_inv(State(state): State>) "#, - html_content + view ); - Html(html_content).into_response() } pub(crate) async fn handler_oob_qr(State(state): State>) -> Response { let mut fs = state.filesystem.lock().unwrap(); - let (server_public_domain, server_local_port, storage_dirpath) = - match get_environment_variables() { - Ok(result) => result, + let (server_public_domain, storage_dirpath) = match get_environment_variables() { + Ok(result) => result, + Err(err) => { + tracing::error!("Error: {err:?}"); + return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response(); + } + }; + + let oob_inv = + match retrieve_or_generate_oob_inv(&mut *fs, &server_public_domain, &storage_dirpath) { + Ok(oob_inv) => oob_inv, Err(err) => { - tracing::error!("Error: {err:?}"); - return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error") + tracing::error!("Failed to retrieve or generate oob invitation: {err:?}"); + return ( + StatusCode::SERVICE_UNAVAILABLE, + "Could not process request at this time. Please try again later", + ) .into_response(); } }; - let oob_inv = match retrieve_or_generate_oob_inv( - &mut *fs, - &server_public_domain, - &server_local_port, - &storage_dirpath, - ) { - Ok(oob_inv) => oob_inv, - Err(err) => { - tracing::error!("Failed to retrieve or generate oob invitation: {err:?}"); - return ( - StatusCode::SERVICE_UNAVAILABLE, - "Could not process request at this time. Please try again later", - ) - .into_response(); - } - }; - let image_data = match retrieve_or_generate_qr_image(&mut *fs, &storage_dirpath, &oob_inv) { Ok(data) => data, Err(err) => { @@ -126,33 +144,27 @@ pub(crate) async fn handler_landing_page_oob( State(state): State>, ) -> Response { let mut fs = state.filesystem.lock().unwrap(); - let (server_public_domain, server_local_port, storage_dirpath) = - match get_environment_variables() { - Ok(result) => result, + let (server_public_domain, storage_dirpath) = match get_environment_variables() { + Ok(result) => result, + Err(err) => { + tracing::error!("Error: {err:?}"); + return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response(); + } + }; + + let oob_inv = + match retrieve_or_generate_oob_inv(&mut *fs, &server_public_domain, &storage_dirpath) { + Ok(oob_inv) => oob_inv, Err(err) => { - tracing::error!("Error: {err:?}"); - return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error") + tracing::error!("Failed to retrieve or generate oob invitation: {err:?}"); + return ( + StatusCode::SERVICE_UNAVAILABLE, + "Could not process request at this time. Please try again later", + ) .into_response(); } }; - let oob_inv = match retrieve_or_generate_oob_inv( - &mut *fs, - &server_public_domain, - &server_local_port, - &storage_dirpath, - ) { - Ok(oob_inv) => oob_inv, - Err(err) => { - tracing::error!("Failed to retrieve or generate oob invitation: {err:?}"); - return ( - StatusCode::SERVICE_UNAVAILABLE, - "Could not process request at this time. Please try again later", - ) - .into_response(); - } - }; - let image_data = match retrieve_or_generate_qr_image(&mut *fs, &storage_dirpath, &oob_inv) { Ok(data) => data, Err(err) => { @@ -200,15 +212,12 @@ pub(crate) async fn handler_landing_page_oob( .into_response() } -fn get_environment_variables() -> Result<(String, String, String), Box> { +fn get_environment_variables() -> Result<(String, String), Box> { let server_public_domain = std::env::var("SERVER_PUBLIC_DOMAIN") .map_err(|_| "SERVER_PUBLIC_DOMAIN env variable required")?; - let server_local_port = std::env::var("SERVER_LOCAL_PORT") - .map_err(|_| "SERVER_LOCAL_PORT env variable required")?; - let storage_dirpath = std::env::var("STORAGE_DIRPATH").map_err(|_| "STORAGE_DIRPATH env variable required")?; - Ok((server_public_domain, server_local_port, storage_dirpath)) + Ok((server_public_domain, storage_dirpath)) }