Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ build/
# Generated files
signatures.json
scripts
.env.build
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nginx-x402"
version = "1.3.3"
version = "1.3.4"
edition = "2021"
description = "Pure Rust Nginx module for x402 HTTP micropayment protocol"
authors = ["Ryan Kung <ryan@polyjuice.io>"]
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ http {
**Advanced Configuration:**
- `x402_resource <path>` - Resource path or full URL (default: automatically builds full URL from request: `scheme://host/path`)
- `x402_timeout <seconds>` - Facilitator API timeout (1-300, default: 10)
- `x402_ttl <seconds>` - Time-to-live for payment authorization validity (1-3600, default: 60). Controls the maximum time window for payment authorization timestamps.
- `x402_facilitator_fallback <mode>` - Fallback on error: `error` (500) or `pass` (default: `error`)
- `x402_metrics on|off` - Enable Prometheus metrics endpoint

Expand Down
11 changes: 11 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
nginx-x402 (1.3.4-1) unstable; urgency=medium

* Add timestamp logging for payment verification debugging
* Log current timestamp when processing X-PAYMENT header
* Log current timestamp in facilitator response for time-related issue debugging
* Include maxTimeoutSeconds in payment header logs
* Add x402_ttl configuration directive for customizing payment authorization TTL (1-3600 seconds, default: 60)
* Set PaymentRequirements.max_timeout_seconds from configuration
* Add comprehensive unit and integration tests for timestamp logging
* Improve code quality: all clippy warnings resolved, formatting applied

nginx-x402 (1.3.3-1) unstable; urgency=medium

* Add detailed logging for facilitator payment verification responses
Expand Down
8 changes: 7 additions & 1 deletion rpm/nginx-x402.spec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
%define moduledir %{_libdir}/nginx/modules

Name: nginx-x402
Version: 1.3.1
Version: 1.3.4
Release: 1%{?dist}
Summary: Pure Rust Nginx module for x402 HTTP micropayment protocol
License: AGPL-3.0
Expand Down Expand Up @@ -673,6 +673,12 @@ fi
%{_datadir}/%{name}/

%changelog
* Wed Dec 03 2025 Ryan Kung <ryan@polyjuice.io> - 1.3.4-1
- Add timestamp logging for payment verification debugging
- Add x402_ttl configuration directive for customizing payment authorization TTL
- Include maxTimeoutSeconds in payment header logs
- Add comprehensive unit and integration tests for timestamp logging

* Mon Dec 01 2025 Ryan Kung <ryan@polyjuice.io> - 1.3.1-1
- Version bump to 1.3.1
- Add retry logic to integration tests for better stability
Expand Down
15 changes: 13 additions & 2 deletions src/ngx_module/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ use basic::{
ngx_http_x402_pay_to,
};
use network::{ngx_http_x402_network, ngx_http_x402_network_id};
use other::{ngx_http_x402_facilitator_fallback, ngx_http_x402_metrics, ngx_http_x402_timeout};
use other::{
ngx_http_x402_facilitator_fallback, ngx_http_x402_metrics, ngx_http_x402_timeout,
ngx_http_x402_ttl,
};

/// Configuration commands array
///
Expand All @@ -52,7 +55,7 @@ use other::{ngx_http_x402_facilitator_fallback, ngx_http_x402_metrics, ngx_http_
/// - Offset: offset within the config structure
/// - Post: post-processing function (if any)
#[no_mangle]
pub static mut ngx_http_x402_commands: [ngx_command_t; 14] = [
pub static mut ngx_http_x402_commands: [ngx_command_t; 15] = [
ngx_command_t {
name: ngx_string!("x402"),
type_: (ngx::ffi::NGX_HTTP_MAIN_CONF
Expand Down Expand Up @@ -152,6 +155,14 @@ pub static mut ngx_http_x402_commands: [ngx_command_t; 14] = [
offset: 0,
post: ptr::null_mut(),
},
ngx_command_t {
name: ngx_string!("x402_ttl"),
type_: (ngx::ffi::NGX_HTTP_LOC_CONF | ngx::ffi::NGX_CONF_TAKE1) as usize,
set: Some(ngx_http_x402_ttl),
conf: ngx::ffi::NGX_HTTP_LOC_CONF_OFFSET,
offset: 0,
post: ptr::null_mut(),
},
ngx_command_t {
name: ngx_string!("x402_metrics"),
type_: (ngx::ffi::NGX_HTTP_LOC_CONF | ngx::ffi::NGX_CONF_FLAG) as usize,
Expand Down
45 changes: 45 additions & 0 deletions src/ngx_module/commands/other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! This module contains handlers for miscellaneous configuration directives:
//! - `x402_timeout`
//! - `x402_facilitator_fallback`
//! - `x402_ttl`
//! - `x402_metrics`

use crate::ngx_module::commands::common::copy_string_to_pool;
Expand Down Expand Up @@ -51,6 +52,50 @@ pub(crate) unsafe extern "C" fn ngx_http_x402_timeout(
ptr::null_mut()
}

/// Parse `x402_ttl` directive
///
/// Sets the time-to-live (TTL) for payment authorization validity.
/// This controls the maximum time window for payment authorization timestamps.
///
/// # Arguments
/// - `cf`: Nginx configuration context
/// - `_cmd`: Command structure (unused)
/// - `conf`: Module configuration structure
///
/// # Returns
/// - `null` on success
/// - Error pointer on failure
pub(crate) unsafe extern "C" fn ngx_http_x402_ttl(
cf: *mut ngx_conf_t,
_cmd: *mut ngx_command_t,
conf: *mut core::ffi::c_void,
) -> *mut c_char {
let conf = conf.cast::<X402Config>();
if conf.is_null() {
return ngx::core::NGX_CONF_ERROR.cast::<c_char>();
}

let args = (*cf).args;
if args.is_null() || (*args).nelts < 2 {
return ngx::core::NGX_CONF_ERROR.cast::<c_char>();
}

// elts is a pointer to an array of ngx_str_t, not an array of pointers
let elts = (*args).elts.cast::<ngx_str_t>();
let value_str = *elts.add(1);

match copy_string_to_pool(cf, value_str) {
Some(allocated_str) => {
(*conf).ttl_str = allocated_str;
}
None => {
return ngx::core::NGX_CONF_ERROR.cast::<c_char>();
}
}

ptr::null_mut()
}

/// Parse `x402_facilitator_fallback` directive
pub(crate) unsafe extern "C" fn ngx_http_x402_facilitator_fallback(
cf: *mut ngx_conf_t,
Expand Down
30 changes: 30 additions & 0 deletions src/ngx_module/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub struct X402Config {
pub asset_decimals_str: ngx_str_t, // Token decimals (e.g., "6" for USDC, "18" for most ERC-20)
pub timeout_str: ngx_str_t, // Timeout in seconds (e.g., "10")
pub facilitator_fallback_str: ngx_str_t, // Fallback mode: "error" or "pass"
pub ttl_str: ngx_str_t, // TTL for payment authorization validity in seconds (e.g., "60")
}

/// Facilitator fallback mode
Expand All @@ -47,6 +48,7 @@ pub struct ParsedX402Config {
pub asset_decimals: Option<u8>, // Token decimals (default: 6 for USDC, typically 18 for ERC-20)
pub timeout: Option<Duration>, // Timeout for facilitator requests
pub facilitator_fallback: FacilitatorFallback, // Fallback behavior when facilitator fails
pub ttl: Option<u32>, // TTL for payment authorization validity in seconds (default: 60)
}

impl X402Config {
Expand Down Expand Up @@ -249,6 +251,33 @@ impl X402Config {
}
};

// Parse TTL (payment authorization validity time)
let ttl = if self.ttl_str.len == 0 {
None // Use default (60) from PaymentRequirements
} else {
let ngx_str = unsafe { NgxStr::from_ngx_str(self.ttl_str) };
let ttl_str = ngx_str
.to_str()
.map_err(|_| ConfigError::from("Invalid ttl string encoding"))?;

let ttl_value = ttl_str
.parse::<u32>()
.map_err(|e| ConfigError::from(format!("Invalid ttl format: {e}")))?;

// Validate TTL range (1 second to 3600 seconds / 1 hour)
// This controls the maximum time window for payment authorization validity
if ttl_value < 1 {
return Err(ConfigError::from("ttl must be at least 1 second"));
}
if ttl_value > 3600 {
return Err(ConfigError::from(
"ttl must be at most 3600 seconds (1 hour)",
));
}

Some(ttl_value)
};

Ok(ParsedX402Config {
enabled: self.enabled != 0,
amount,
Expand All @@ -262,6 +291,7 @@ impl X402Config {
asset_decimals,
timeout,
facilitator_fallback,
ttl,
})
}
}
12 changes: 11 additions & 1 deletion src/ngx_module/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,19 @@ pub fn x402_handler_impl(r: &mut Request, config: &ParsedX402Config) -> Result<H
let payment_header = get_header_value(r, "X-PAYMENT");

if let Some(payment_b64) = payment_header {
// Get current timestamp for debugging time-related issues
let current_timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();

log_debug(
Some(r),
"X-PAYMENT header found, validating and verifying payment",
&format!(
"X-PAYMENT header found, validating and verifying payment, current_timestamp={}, maxTimeoutSeconds={}",
current_timestamp,
requirements.max_timeout_seconds
),
);

// Record verification attempt
Expand Down
3 changes: 3 additions & 0 deletions src/ngx_module/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ unsafe extern "C" fn merge_loc_conf(
if conf_mut.facilitator_fallback_str.len == 0 {
conf_mut.facilitator_fallback_str = prev_conf.facilitator_fallback_str;
}
if conf_mut.ttl_str.len == 0 {
conf_mut.ttl_str = prev_conf.ttl_str;
}

// Note: Handler is set in ngx_http_x402 command handler when x402 on; is parsed
// We cannot set handler here in merge_loc_conf because accessing clcf during merging
Expand Down
7 changes: 7 additions & 0 deletions src/ngx_module/requirements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ pub fn create_requirements(
config.description.as_deref().unwrap_or(""),
);

// Set max_timeout_seconds if configured, otherwise use default (60 seconds)
// This field controls the maximum time window for payment authorization validity.
// The TTL is used by the facilitator service to validate payment authorization timestamps.
if let Some(ttl_value) = config.ttl {
requirements.max_timeout_seconds = ttl_value;
}

// Set MIME type - always set a value (use provided or default to application/json)
// PaymentRequirements has a public mime_type field that can be set directly
let final_mime_type = if let Some(mime) = mime_type {
Expand Down
12 changes: 10 additions & 2 deletions src/ngx_module/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,21 @@ pub async fn verify_payment(
let verify_future = client.verify(&payment_payload, requirements);
match timeout(timeout_duration, verify_future).await {
Ok(Ok(response)) => {
// Get current timestamp for debugging time-related issues
let current_timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();

// Log facilitator response details for debugging
// Include current timestamp to help debug time-related validation issues
log_debug(
None,
&format!(
"Facilitator verify response: is_valid={}, invalid_reason={:?}",
"Facilitator verify response: is_valid={}, invalid_reason={:?}, current_timestamp={}",
response.is_valid,
response.invalid_reason.as_deref().unwrap_or("none")
response.invalid_reason.as_deref().unwrap_or("none"),
current_timestamp
),
);
Ok(response.is_valid)
Expand Down
1 change: 1 addition & 0 deletions tests/config_validation_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod tests {
asset_decimals_str: ngx::ffi::ngx_str_t::default(),
timeout_str: ngx::ffi::ngx_str_t::default(),
facilitator_fallback_str: ngx::ffi::ngx_str_t::default(),
ttl_str: ngx::ffi::ngx_str_t::default(),
}
}

Expand Down
3 changes: 3 additions & 0 deletions tests/docker_integration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,6 @@ pub mod config_tests;

#[cfg(feature = "integration-test")]
pub mod header_passthrough_tests;

#[cfg(feature = "integration-test")]
pub mod timestamp_tests;
Loading