Files
sausage/host/tests/it/hook_api.rs
T

105 lines
3.5 KiB
Rust

use super::api_transport::{Listener, Options as ApiOptions};
// The hook and native listener share ceremony state without exposing the native listener to guests.
use sausage::{
api::Api,
cred::{Caller, Cred},
exec::{Command, Exec, Options},
process::Runtime,
world::World,
};
use std::{
io::{Read, Write},
sync::Arc,
};
use super::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:8090";
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")
.header("origin", origin)
.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\nOrigin: {origin}\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(())
}