use serde_json::Value;
use trust_tasks_rs::{RejectReason, TrustTask};
use vta_sdk::protocols::credential_exchange::{ISSUE, IssueBody, PresentBody, RequestBody};
use vta_sdk::protocols::join_requests::{
JOIN_REQUEST_SUBMIT_RECEIPT_TYPE, JoinRequestSubmitReceiptBody,
};
use super::JoinAuthCtx;
use super::helpers::{
TrustTaskOutcome, acknowledge, app_error_to_reject, parse_payload, reject_with,
};
use crate::credentials::delivery::{Thread, push_document};
use crate::server::AppState;
fn thread_of(doc: &TrustTask<Value>) -> &str {
doc.thread_id.as_deref().unwrap_or(&doc.id)
}
pub(super) async fn handle_request(
state: &AppState,
ctx: &JoinAuthCtx,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
let holder_did = match super::resolve_holder(state, ctx, &doc).await {
Ok(did) => did,
Err(reject) => return reject,
};
let body: RequestBody = match parse_payload(&doc) {
Ok(b) => b,
Err(reject) => return reject,
};
let response = match crate::credentials::redeem(
&state.join_requests_ks,
&body.credential_request,
chrono::Utc::now(),
&crate::credentials::vm_resolver::DidVmResolver::new(state.did_resolver.clone()),
)
.await
{
Ok(r) => r,
Err(e) => return app_error_to_reject(&doc, &e),
};
let issue = IssueBody {
credential_response: Some(response),
sealed: None,
};
let payload = match serde_json::to_value(&issue) {
Ok(v) => v,
Err(e) => {
return reject_with(
&doc,
RejectReason::InternalError {
reason: format!("issue serialise: {e}"),
},
);
}
};
if let Err(e) = push_document(
state,
&holder_did,
ISSUE,
payload,
Thread::Reply(thread_of(&doc)),
)
.await
{
return app_error_to_reject(&doc, &e);
}
acknowledge(&doc)
}
pub(super) async fn handle_present(
state: &AppState,
ctx: &JoinAuthCtx,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
let sender_did = match super::resolve_holder(state, ctx, &doc).await {
Ok(did) => did,
Err(reject) => return reject,
};
let body: PresentBody = match parse_payload(&doc) {
Ok(b) => b,
Err(reject) => return reject,
};
let Some(thread_id) = doc.thread_id.clone() else {
return reject_with(
&doc,
RejectReason::MalformedRequest {
reason: "present carries no threadId to correlate its challenge".into(),
},
);
};
let now = chrono::Utc::now();
let challenge = match crate::credentials::present_challenge::consume(
&state.join_requests_ks,
&thread_id,
now,
)
.await
{
Ok(c) => c,
Err(e) => return app_error_to_reject(&doc, &e),
};
let outcome = match crate::routes::join_requests::present::present_and_decide_join(
state,
&body.vp_token,
&challenge.aud,
&challenge.nonce,
&thread_id,
challenge.criterion.as_deref(),
ctx.transport,
now,
)
.await
{
Ok(o) => o,
Err(e) => return app_error_to_reject(&doc, &e),
};
let applicant_did = outcome.request.applicant_did.clone();
if let Some(admit) = outcome.admit.as_deref() {
if let Err(e) = crate::credentials::delivery::deliver_membership_credentials(
state,
&applicant_did,
admit,
)
.await
{
tracing::warn!(holder = %applicant_did, request = %outcome.request.id, error = %e, "membership-credential delivery failed; credential is issued and can be re-delivered");
} else {
tracing::info!(holder = %applicant_did, request = %outcome.request.id, "queued membership credentials for delivery to holder");
}
}
let status = serde_json::to_value(outcome.request.status)
.ok()
.and_then(|v| v.as_str().map(str::to_string))
.unwrap_or_default();
let receipt = JoinRequestSubmitReceiptBody {
request_id: outcome.request.id,
status,
};
let payload = match serde_json::to_value(&receipt) {
Ok(v) => v,
Err(e) => {
return reject_with(
&doc,
RejectReason::InternalError {
reason: format!("receipt serialise: {e}"),
},
);
}
};
if let Err(e) = push_document(
state,
&sender_did,
JOIN_REQUEST_SUBMIT_RECEIPT_TYPE,
payload,
Thread::Reply(&thread_id),
)
.await
{
tracing::warn!(to = %sender_did, error = %e, "submit receipt could not be queued; the join is decided and join-requests/status reports it");
}
acknowledge(&doc)
}