103 lines
3.4 KiB
Rust
103 lines
3.4 KiB
Rust
//! 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,
|
|
};
|
|
|
|
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: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(())
|
|
}
|