Share owned host API state between HTTP hooks and native pages
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
//! The hook and native listener share ceremony state without exposing the native listener to guests.
|
||||
use sausage::{
|
||||
api::{Api, Listener, Options as ApiOptions},
|
||||
cred::{Caller, Cred},
|
||||
exec::{Command, Exec, Options},
|
||||
process::Runtime,
|
||||
world::World,
|
||||
};
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
sync::Arc,
|
||||
};
|
||||
#[path = "hook_guest.rs"]
|
||||
mod hook_guest;
|
||||
|
||||
#[tokio::test]
|
||||
async fn one_api_for_pages_and_guest_requests() -> anyhow::Result<()> {
|
||||
use webauthn_authenticator_rs::{WebauthnAuthenticator, softpasskey::SoftPasskey};
|
||||
let dir = tempfile::tempdir()?;
|
||||
let world = World::open(dir.path())?;
|
||||
std::fs::create_dir_all(dir.path().join("world/bin"))?;
|
||||
std::fs::copy(
|
||||
hook_guest::component(),
|
||||
dir.path().join("world/bin/guest.wasm"),
|
||||
)?;
|
||||
let cred = Arc::new(Cred::open(&world.host_dir)?);
|
||||
let origin = "http://localhost:8081";
|
||||
let api = Arc::new(Api::open(
|
||||
cred.clone(),
|
||||
&world.host_dir,
|
||||
"localhost",
|
||||
origin,
|
||||
)?);
|
||||
let runtime = Runtime::new_with_hooks(
|
||||
world,
|
||||
cred,
|
||||
sausage::process::default_script(),
|
||||
Some(api.clone()),
|
||||
)?;
|
||||
let listener = Listener::start_with_api(
|
||||
api.clone(),
|
||||
ApiOptions {
|
||||
api_listen: "127.0.0.1:0".parse()?,
|
||||
api_rp_id: "localhost".into(),
|
||||
api_origin: origin.into(),
|
||||
},
|
||||
)?;
|
||||
runtime
|
||||
.network_policy()
|
||||
.deny_host_port(listener.address().port());
|
||||
let response = sausage::api::handle(
|
||||
&api,
|
||||
Caller {
|
||||
program: "/bin/guest.wasm".into(),
|
||||
pid: 1,
|
||||
conversations: vec![],
|
||||
},
|
||||
http::Request::post("http://host.sausage/cred/passkeys/register/start")
|
||||
.body(bytes::Bytes::from_static(b"{\"label\":\"shared\"}"))?,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(response.status(), 200);
|
||||
let challenge: serde_json::Value = serde_json::from_slice(response.body())?;
|
||||
let mut authenticator = WebauthnAuthenticator::new(SoftPasskey::new(true));
|
||||
let registration = authenticator.do_registration(
|
||||
url::Url::parse(origin)?,
|
||||
serde_json::from_value(challenge["options"].clone())?,
|
||||
)?;
|
||||
let body = serde_json::to_vec(
|
||||
&serde_json::json!({"challenge_id":challenge["id"], "response": registration}),
|
||||
)?;
|
||||
let mut connection = std::net::TcpStream::connect(listener.address())?;
|
||||
write!(
|
||||
connection,
|
||||
"POST /cred/passkeys/register/finish HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nContent-Length: {}\r\n\r\n",
|
||||
body.len()
|
||||
)?;
|
||||
connection.write_all(&body)?;
|
||||
let mut reply = String::new();
|
||||
connection.read_to_string(&mut reply)?;
|
||||
assert!(reply.starts_with("HTTP/1.1 200"));
|
||||
for (uri, expected) in [
|
||||
("http://host.sausage/cred/passkeys".to_owned(), "shared"),
|
||||
(format!("http://{}/whoami", listener.address()), "error"),
|
||||
] {
|
||||
let mut out = Vec::new();
|
||||
let code = runtime
|
||||
.exec(
|
||||
Command::Argv(vec!["/bin/guest.wasm".into(), uri, "GET".into()]),
|
||||
Options {
|
||||
cwd: "/".into(),
|
||||
timeout: Some(std::time::Duration::from_secs(30)),
|
||||
..Default::default()
|
||||
},
|
||||
&mut |_, b| out.extend_from_slice(b),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(code, 0);
|
||||
assert!(String::from_utf8(out)?.contains(expected));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user