diff --git a/tests/test_full_flow.rs b/tests/test_full_flow.rs new file mode 100644 index 0000000..38a11da --- /dev/null +++ b/tests/test_full_flow.rs @@ -0,0 +1,152 @@ +mod common; + +use axum_test::TestServer; +use serde_json::json; +use webfingerd::handler; + +#[tokio::test] +async fn test_full_webfinger_flow() { + let state = common::test_state().await; + let app = handler::router(state.clone()); + let server = TestServer::new(app); + + // 1. Register domain + let create_resp = server + .post("/api/v1/domains") + .json(&json!({"domain": "social.alice.example", "challenge_type": "dns-01"})) + .await; + create_resp.assert_status(axum::http::StatusCode::CREATED); + let body: serde_json::Value = create_resp.json(); + let domain_id = body["id"].as_str().unwrap().to_string(); + let reg_secret = body["registration_secret"].as_str().unwrap().to_string(); + + // 2. Verify domain (MockChallengeVerifier always succeeds) + let verify_resp = server + .post(&format!("/api/v1/domains/{domain_id}/verify")) + .json(&json!({"registration_secret": reg_secret})) + .await; + let owner_token = verify_resp.json::()["owner_token"] + .as_str() + .unwrap() + .to_string(); + + // 3. Create service token for ActivityPub (oxifed) + let token_resp = server + .post(&format!("/api/v1/domains/{domain_id}/tokens")) + .add_header("Authorization", format!("Bearer {owner_token}")) + .json(&json!({ + "name": "oxifed", + "allowed_rels": ["self", "http://webfinger.net/rel/profile-page"], + "resource_pattern": "acct:*@social.alice.example" + })) + .await; + token_resp.assert_status(axum::http::StatusCode::CREATED); + let ap_token = token_resp.json::()["token"] + .as_str() + .unwrap() + .to_string(); + + // 4. Create service token for OIDC (barycenter) + let token_resp = server + .post(&format!("/api/v1/domains/{domain_id}/tokens")) + .add_header("Authorization", format!("Bearer {owner_token}")) + .json(&json!({ + "name": "barycenter", + "allowed_rels": ["http://openid.net/specs/connect/1.0/issuer"], + "resource_pattern": "acct:*@social.alice.example" + })) + .await; + let oidc_token = token_resp.json::()["token"] + .as_str() + .unwrap() + .to_string(); + + // 5. oxifed registers ActivityPub links with aliases + server + .post("/api/v1/links") + .add_header("Authorization", format!("Bearer {ap_token}")) + .json(&json!({ + "resource_uri": "acct:alice@social.alice.example", + "rel": "self", + "href": "https://social.alice.example/users/alice", + "type": "application/activity+json", + "aliases": ["https://social.alice.example/@alice"] + })) + .await + .assert_status(axum::http::StatusCode::CREATED); + + server + .post("/api/v1/links") + .add_header("Authorization", format!("Bearer {ap_token}")) + .json(&json!({ + "resource_uri": "acct:alice@social.alice.example", + "rel": "http://webfinger.net/rel/profile-page", + "href": "https://social.alice.example/@alice", + "type": "text/html" + })) + .await + .assert_status(axum::http::StatusCode::CREATED); + + // 6. barycenter registers OIDC issuer link + server + .post("/api/v1/links") + .add_header("Authorization", format!("Bearer {oidc_token}")) + .json(&json!({ + "resource_uri": "acct:alice@social.alice.example", + "rel": "http://openid.net/specs/connect/1.0/issuer", + "href": "https://auth.alice.example" + })) + .await + .assert_status(axum::http::StatusCode::CREATED); + + // 7. Query WebFinger — should return all three links + let wf_resp = server + .get("/.well-known/webfinger") + .add_query_param("resource", "acct:alice@social.alice.example") + .await; + wf_resp.assert_status_ok(); + let jrd: serde_json::Value = wf_resp.json(); + + assert_eq!(jrd["subject"], "acct:alice@social.alice.example"); + assert_eq!(jrd["aliases"][0], "https://social.alice.example/@alice"); + + let links = jrd["links"].as_array().unwrap(); + assert_eq!(links.len(), 3); + + // 8. Filter by rel=self — verify only 1 link returned, aliases still present + let wf_resp = server + .get("/.well-known/webfinger") + .add_query_param("resource", "acct:alice@social.alice.example") + .add_query_param("rel", "self") + .await; + let jrd: serde_json::Value = wf_resp.json(); + let links = jrd["links"].as_array().unwrap(); + assert_eq!(links.len(), 1); + assert_eq!(links[0]["rel"], "self"); + // aliases should still be present despite rel filter + assert!(jrd["aliases"].is_array()); + + // 9. Verify scope isolation: oxifed can't register OIDC links + let bad_resp = server + .post("/api/v1/links") + .add_header("Authorization", format!("Bearer {ap_token}")) + .json(&json!({ + "resource_uri": "acct:alice@social.alice.example", + "rel": "http://openid.net/specs/connect/1.0/issuer", + "href": "https://evil.com" + })) + .await; + bad_resp.assert_status(axum::http::StatusCode::FORBIDDEN); + + // 10. Verify scope isolation: barycenter can't register AP links + let bad_resp = server + .post("/api/v1/links") + .add_header("Authorization", format!("Bearer {oidc_token}")) + .json(&json!({ + "resource_uri": "acct:alice@social.alice.example", + "rel": "self", + "href": "https://evil.com" + })) + .await; + bad_resp.assert_status(axum::http::StatusCode::FORBIDDEN); +}