use affinidi_messaging_didcomm::Message;
use affinidi_openid4vci::issuer::create_credential_response;
use affinidi_vc::VerifiableCredential;
use serde_json::Value as JsonValue;
use uuid::Uuid;
use vta_sdk::protocols::credential_exchange::{ISSUE as CREDENTIAL_ISSUE_TYPE, IssueBody};
use vti_common::error::AppError;
use crate::ceremony::AdmitOutcome;
use crate::server::AppState;
pub(crate) async fn deliver_membership_credentials(
state: &AppState,
holder_did: &str,
admit: &AdmitOutcome,
) -> Result<(), AppError> {
deliver_credentials(state, holder_did, &[&admit.vmc, &admit.role_vec]).await
}
pub(crate) async fn deliver_credentials(
state: &AppState,
holder_did: &str,
credentials: &[&VerifiableCredential],
) -> Result<(), AppError> {
for credential in credentials {
let credential_json = serde_json::to_value(credential)
.map_err(|e| AppError::Internal(format!("issued credential serialise: {e}")))?;
let body = issue_message_body(credential_json)?;
let msg_id = Uuid::new_v4().to_string();
push_to_holder(state, holder_did, &msg_id, CREDENTIAL_ISSUE_TYPE, body).await?;
}
Ok(())
}
fn issue_message_body(credential_json: JsonValue) -> Result<JsonValue, AppError> {
let issue = IssueBody {
credential_response: Some(create_credential_response(credential_json, None, None)),
sealed: None,
};
serde_json::to_value(&issue)
.map_err(|e| AppError::Internal(format!("issue body serialise: {e}")))
}
pub(crate) async fn push_to_holder(
state: &AppState,
holder_did: &str,
msg_id: &str,
msg_type: &str,
body: JsonValue,
) -> Result<(), AppError> {
let vtc_did = state
.config
.read()
.await
.vtc_did
.clone()
.filter(|d| !d.is_empty())
.ok_or_else(|| AppError::Internal("VTC DID not configured".into()))?;
let message = Message::build(msg_id.to_string(), msg_type.to_string(), body)
.from(vtc_did)
.to(holder_did.to_string())
.finalize();
state.send_to_member(holder_did, message).await
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn issue_message_body_matches_the_vta_receive_shape() {
let vmc = json!({
"@context": ["https://www.w3.org/ns/credentials/v2"],
"type": ["VerifiableCredential", "MembershipCredential"],
"issuer": "did:web:vtc.example",
"credentialSubject": { "id": "did:key:zHolder", "community": "acme" },
"proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022" },
});
let body = issue_message_body(vmc.clone()).expect("wrap issue body");
let issue: IssueBody = serde_json::from_value(body).expect("parse as IssueBody");
assert!(
issue.sealed.is_none(),
"a proven holder gets authcrypt, not a seal"
);
let credential = issue
.credential_response
.expect("credential_response present")
.credential
.expect("credential present");
assert_eq!(
credential, vmc,
"the delivered credential round-trips intact"
);
}
}