use std::sync::Arc;
use serde_json::Value;
use trust_tasks_rs::TrustTask;
use vti_common::auth::{DiProofError, TrustTaskVmResolver};
tokio::task_local! {
static RECEIVED: Arc<Value>;
}
pub(crate) fn scope<F>(
received: Arc<Value>,
f: F,
) -> tokio::task::futures::TaskLocalFuture<Arc<Value>, F>
where
F: std::future::Future,
{
RECEIVED.scope(received, f)
}
fn received_form_of(doc: &TrustTask<Value>) -> Option<Arc<Value>> {
let received = RECEIVED.try_with(Arc::clone).ok()?;
let parsed: TrustTask<Value> = serde_json::from_value((*received).clone()).ok()?;
(&parsed == doc).then_some(received)
}
pub(crate) async fn verify_trust_task_proof(
doc: &TrustTask<Value>,
resolver: &TrustTaskVmResolver,
) -> Result<String, DiProofError> {
match received_form_of(doc) {
Some(received) => {
vti_common::auth::verify_trust_task_proof_value(&received, resolver).await
}
None => vti_common::auth::verify_trust_task_proof_with(doc, resolver).await,
}
}
pub(crate) async fn verify_approval_proof(
doc: &TrustTask<Value>,
resolver: &TrustTaskVmResolver,
) -> Result<String, DiProofError> {
match received_form_of(doc) {
Some(received) => vti_common::auth::verify_approval_proof_value(&received, resolver).await,
None => vti_common::auth::verify_approval_proof_with(doc, resolver).await,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn javascript_decision() -> Value {
let now = crate::test_support::javascript_whole_second_now();
let mut doc = json!({
"id": "urn:uuid:45454545-4545-4545-8545-000000000001",
"type": "https://trusttasks.org/spec/task-consent/decision/0.1",
"issuer": crate::test_support::test_admin_did().0,
"recipient": "did:key:z6MkVta",
"issuedAt": now,
"threadId": null,
"payload": { "decision": "approve" },
});
crate::test_support::sign_received_as(
crate::test_support::TEST_ADMIN_SEED[0],
"assertionMethod",
&now,
&mut doc,
);
doc
}
#[tokio::test]
async fn vti_45_a_handler_verifies_the_recorded_document_as_received() {
let received = javascript_decision();
let doc: TrustTask<Value> = serde_json::from_value(received.clone()).unwrap();
let resolver = TrustTaskVmResolver::did_key_only();
let signer = crate::test_support::test_admin_did().0;
let inside = scope(Arc::new(received.clone()), async {
(
verify_approval_proof(&doc, &resolver).await,
verify_trust_task_proof(&doc, &resolver).await,
)
})
.await;
assert_eq!(inside.0.as_deref().ok(), Some(signer.as_str()));
assert_eq!(inside.1.as_deref().ok(), Some(signer.as_str()));
assert!(verify_approval_proof(&doc, &resolver).await.is_err());
}
#[tokio::test]
async fn vti_45_the_recorded_document_never_vouches_for_another() {
let received = javascript_decision();
let mut other: TrustTask<Value> = serde_json::from_value(received.clone()).unwrap();
other.payload = json!({ "decision": "deny" });
let resolver = TrustTaskVmResolver::did_key_only();
let result = scope(Arc::new(received), async {
verify_approval_proof(&other, &resolver).await
})
.await;
assert!(
matches!(result, Err(DiProofError::VerifyFailed(_))),
"{result:?}"
);
}
#[tokio::test]
async fn vti_45_an_approval_still_needs_an_assertion_proof() {
let now = crate::test_support::javascript_whole_second_now();
let mut received = javascript_decision();
crate::test_support::sign_received_as(
crate::test_support::TEST_ADMIN_SEED[0],
"authentication",
&now,
&mut received,
);
let doc: TrustTask<Value> = serde_json::from_value(received.clone()).unwrap();
let resolver = TrustTaskVmResolver::did_key_only();
let result = scope(Arc::new(received), async {
verify_approval_proof(&doc, &resolver).await
})
.await;
assert!(
matches!(result, Err(DiProofError::WrongPurpose { .. })),
"{result:?}"
);
}
}