//! Phase 5 tests: RFC 9421 HTTP Message Signatures
mod support;
use std::time::{Duration, Instant};
use support::ap::RemoteActor;
use support::{Client, Server, free_port};
/// The password the tests register and log in with.
const PASSWORD: &str = "correct horse battery staple";
/// Register a user through the web form.
fn register(client: &mut Client, name: &str) {
let email = format!("{name}@example.org");
let response = client.post_form(
"/register",
&[("name", name), ("email", &email), ("password", PASSWORD)],
);
assert_eq!(
response.status, 303,
"registering should log in\n{}",
response.body
);
}
/// Test: RemoteActor follows alice using RFC 9421 signature → Accept(Follow) is delivered
#[tokio::test]
async fn rfc9421_follow_is_accepted() -> miette::Result<()> {
let port = free_port();
let mut server = Server::spawn_federated(&format!("127.0.0.1:{port}"));
server.wait_for_connection(port);
let mut alice_client = Client::new(port);
register(&mut alice_client, "alice");
// Start a remote actor recorder
let recorder_port = free_port();
let recorder = RemoteActor::serve(recorder_port);
// Bob follows alice by sending a Follow activity to her inbox
let follow_json = serde_json::json!({
"@context": "https://www.w3.org/ns/activitystreams",
"id": format!("{}/follow/1", recorder.actor_url),
"type": "Follow",
"actor": recorder.actor_url,
"object": format!("http://127.0.0.1:{port}/~alice")
});
let body = serde_json::to_string(&follow_json).unwrap();
let (signature, signature_input, date, content_digest) = support::ap::signed_post_rfc9421(
&recorder.private_key_pem,
&recorder.key_id,
&format!("127.0.0.1:{port}"),
"/~alice/inbox",
&body,
);
let response = alice_client.post_json_with_headers(
"/~alice/inbox",
&body,
&[
("Signature", &signature),
("Signature-Input", &signature_input),
("Date", &date),
("Content-Digest", &content_digest),
("Content-Type", "application/activity+json"),
],
);
assert_eq!(
response.status, 202,
"RFC 9421 Follow should be accepted. Server log:"
);
// Poll for Accept(Follow) delivery
let deadline = Instant::now() + Duration::from_secs(10);
let mut found_accept = false;
while Instant::now() < deadline {
std::thread::sleep(Duration::from_millis(50));
let deliveries = recorder.recorded_deliveries();
for delivery in &deliveries {
if let Ok(activity) = serde_json::from_str::<serde_json::Value>(&delivery.body) {
if activity.get("type").and_then(|t| t.as_str()) == Some("Accept") {
if let Some(obj) = activity.get("object") {
if obj.get("type").and_then(|t| t.as_str()) == Some("Follow") {
found_accept = true;
break;
}
}
}
}
}
if found_accept {
break;
}
}
assert!(
found_accept,
"alice should send Accept(Follow) for RFC 9421 signed requests. Server log:"
);
Ok(())
}