http injection: fix auth_info regression for deferred_generation

This commit fixes a regression introduced as part of the updated
AAA/ACL code.  Since that code has not shipped in a stable release,
this is not being entered into the changelog.

The issue was that when processing the deferred payloads, we were
trying to parse the `auth_info` key from the metadata as though
it were a json-serialized-string, but it was in fact already parsed
and was actually a json object.

This resulted in a runtime error that caused the message to be
logged as a Bounce like:

```
expected 'auth_info' to be a string value, got Ok(Object (..))
```

This commit adds an explicit integration test for deferred generation so
that we can catch this sort of thing more easily moving forwards, as
well as resolves this issue.
This commit is contained in:
Wez Furlong
2026-01-29 07:52:11 +00:00
parent 524fdd87af
commit dd9df65fe6
3 changed files with 103 additions and 3 deletions
@@ -0,0 +1,95 @@
use crate::kumod::DaemonWithMaildir;
use anyhow::Context;
use k9::assert_equal;
use std::time::Duration;
/// Test HTTP injection with gzip compressed request body
#[tokio::test]
async fn http_inject_deferred() -> anyhow::Result<()> {
let mut daemon = DaemonWithMaildir::start()
.await
.context("DaemonWithMaildir::start")?;
let payload = serde_json::json!({
"envelope_sender": "sender@example.com",
"recipients": [
{
"email": "user@example.com",
"name": "Test User"
}
],
"deferred_generation": true,
"content": {
"text_body": "Hello {{ name }}! This is a message.",
"subject": "Deferred Generation Test"
}
});
let json_data = serde_json::to_vec(&payload)?;
let client = reqwest::Client::new();
let response = client
.post(&format!(
"http://{}/api/inject/v1",
daemon.source.listener("http")
))
.header("Content-Type", "application/json")
.body(json_data)
.send()
.await?;
anyhow::ensure!(
response.status() == 200,
"Response status: {}",
response.status()
);
let response_json: serde_json::Value = response.json().await?;
eprintln!("response: {response_json:?}");
assert_equal!(
response_json["success_count"],
0,
"deferred always shows zero"
);
assert_equal!(response_json["fail_count"], 0);
daemon
.wait_for_maildir_count(1, Duration::from_secs(10))
.await;
daemon.stop_both().await.context("stop_both")?;
println!("Stopped!");
let delivery_summary = daemon.dump_logs().await.context("dump_logs")?;
k9::snapshot!(
delivery_summary,
"
DeliverySummary {
source_counts: {
Reception: 2,
Delivery: 2,
},
sink_counts: {
Reception: 1,
Delivery: 1,
},
}
"
);
daemon.assert_no_acct_deny().await?;
let mut messages = daemon.extract_maildir_messages()?;
assert_equal!(messages.len(), 1);
let parsed = messages[0].parsed()?;
// Verify the message content was properly expanded
let body = parsed.body().unwrap();
match body {
mailparsing::DecodedBody::Text(text) => {
assert!(text.contains("Hello Test User!"));
assert!(text.contains("This is a message"));
}
_ => panic!("Expected text body"),
}
Ok(())
}
+1
View File
@@ -15,6 +15,7 @@ mod end_to_end_webhook_batch;
mod expires;
mod http_auth;
mod http_inject_compression;
mod http_inject_deferred;
mod http_inject_size_limit;
mod log_oob_arf;
mod maildir_batch;
+7 -3
View File
@@ -979,9 +979,10 @@ impl HttpInjectionGeneratorDispatcher {
.ok_or_else(|| anyhow::anyhow!("received_from metadata is missing!?"))?
.parse()?;
let auth_info: AuthInfo = match msg.get_meta_string("auth_info").await? {
Some(info) => serde_json::from_str(&info)?,
None => {
let auth_info: AuthInfo = match msg.get_meta("auth_info").await? {
obj @ serde_json::Value::Object(_) => serde_json::from_value(obj)
.context("auth_info should be the json repr of AuthInfo")?,
serde_json::Value::Null => {
// We might not have auth_info serialized in the metadata
// if we are upgrading from a prior version that did not
// support AuthInfo, so default it to something approximating
@@ -990,6 +991,9 @@ impl HttpInjectionGeneratorDispatcher {
auth_info.set_peer_address(Some(peer_address));
auth_info
}
other => {
anyhow::bail!("expected optional `auth_info` object, but got {other:?}")
}
};
let via_address = match msg.get_meta_string("received_via").await {