use crate::agent::Agent;
use crate::agent::DOCUMENT_AGENT_SIGNATURE_FIELDNAME;
use crate::agent::document::DocumentTraits;
use crate::error::JacsError;
use crate::replay;
use chrono;
use serde_json::Value;
pub trait PayloadTraits {
fn sign_payload(&mut self, document: Value) -> Result<String, JacsError>;
fn verify_payload(
&mut self,
document_string: String,
max_replay_time_delta: Option<u64>,
) -> Result<Value, JacsError>;
fn verify_payload_with_agent_id(
&mut self,
document_string: String,
max_replay_time_delta: Option<u64>,
) -> Result<(Value, String), JacsError>;
}
impl PayloadTraits for Agent {
fn sign_payload(&mut self, jacs_payload: Value) -> Result<String, JacsError> {
let wrapper_value = serde_json::json!({
"jacs_payload": jacs_payload
});
let wrapper_string = serde_json::to_string(&wrapper_value)?;
let outputfilename: Option<String> = None;
let attachments: Option<String> = None;
let no_save = true;
let docresult = crate::shared::document_create(
self,
&wrapper_string,
None,
outputfilename,
no_save,
attachments.as_deref(),
Some(false),
)?;
Ok(docresult)
}
fn verify_payload(
&mut self,
document_string: String,
max_replay_time_delta: Option<u64>,
) -> Result<Value, JacsError> {
let (payload, _) =
self.verify_payload_with_agent_id(document_string, max_replay_time_delta)?;
Ok(payload.clone())
}
fn verify_payload_with_agent_id(
&mut self,
document_string: String,
max_replay_time_delta_seconds: Option<u64>,
) -> Result<(Value, String), JacsError> {
let doc = self.load_document(&document_string)?;
let document_key = doc.getkey();
let value = doc.getvalue();
self.verify_hash(value)?;
self.verify_external_document_signature(&document_key)?;
let payload = value
.get("jacs_payload")
.ok_or_else(|| JacsError::Internal {
message: "'jacs_payload' field not found".to_string(),
})?;
let date = self.get_document_signature_date(&document_key)?;
let agent_id = self.get_document_signature_agent_id(&document_key)?;
let max_replay_seconds =
max_replay_time_delta_seconds.unwrap_or_else(replay::payload_replay_window_seconds);
let current_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| JacsError::Internal {
message: e.to_string(),
})?
.as_secs();
let date_timestamp = chrono::DateTime::parse_from_rfc3339(&date)
.map_err(|e| JacsError::Internal {
message: e.to_string(),
})?
.timestamp() as u64;
if current_time > date_timestamp && current_time - date_timestamp > max_replay_seconds {
return Err(JacsError::Internal {
message: format!(
"Signature too old: {} seconds (max allowed: {})",
current_time - date_timestamp,
max_replay_seconds
),
});
}
let jti = value
.get(DOCUMENT_AGENT_SIGNATURE_FIELDNAME)
.and_then(|sig| sig.get("jti"))
.and_then(Value::as_str)
.map(str::trim)
.filter(|nonce| !nonce.is_empty())
.ok_or_else(|| JacsError::Internal {
message: "Missing or invalid 'jacsSignature.jti' in payload document".to_string(),
})?;
replay::check_and_store_nonce(&agent_id, jti)?;
Ok((payload.clone(), agent_id))
}
}