#![allow(dead_code)]
use affinidi_did_resolver_cache_sdk::DIDCacheClient;
use async_trait::async_trait;
use serde_json::Value;
use vti_webauthn::{
ResolvedVm, ResolverError, VerifierConfig, VerifyError, VmResolver, multikey,
payload::AssertionPayload, payload::VerifiedAssertion, verify_assertion,
};
pub struct VtaVmResolver {
did_resolver: DIDCacheClient,
}
impl VtaVmResolver {
pub fn new(did_resolver: DIDCacheClient) -> Self {
Self { did_resolver }
}
}
#[async_trait]
impl VmResolver for VtaVmResolver {
async fn resolve_vm(&self, vm_url: &str) -> Result<ResolvedVm, ResolverError> {
let (did, _fragment) = vm_url.split_once('#').ok_or_else(|| {
ResolverError::MalformedVm("verification_method URL must contain '#fragment'".into())
})?;
let resolved = self
.did_resolver
.resolve(did)
.await
.map_err(|e| ResolverError::UnresolvableDid(format!("{e}")))?;
let doc_value: Value = serde_json::to_value(&resolved.doc).map_err(|e| {
ResolverError::MalformedVm(format!("DID document serialise failed: {e}"))
})?;
let vms = doc_value
.get("verificationMethod")
.and_then(|v| v.as_array())
.ok_or(ResolverError::NotFound)?;
let relative = match vm_url.split_once('#') {
Some((_, frag)) => format!("#{frag}"),
None => String::new(),
};
let vm = vms
.iter()
.find(|vm| {
let id = vm.get("id").and_then(|v| v.as_str()).unwrap_or("");
id == vm_url || id == relative
})
.ok_or(ResolverError::NotFound)?;
let multibase_str = vm
.get("publicKeyMultibase")
.and_then(|v| v.as_str())
.ok_or_else(|| {
ResolverError::MalformedVm(
"verification method has no publicKeyMultibase \
(only Multikey-encoded VMs are supported in v0.1)"
.into(),
)
})?;
let (algorithm, public_key_bytes) = multikey::decode_multikey(multibase_str)?;
let controller = vm
.get("controller")
.and_then(|v| v.as_str())
.unwrap_or(did)
.to_string();
Ok(ResolvedVm {
algorithm,
public_key_bytes,
controller,
})
}
}
pub async fn verify_passkey_login(
payload: &AssertionPayload,
expected_challenge: &[u8],
resolver: &VtaVmResolver,
config: &VerifierConfig,
) -> Result<VerifiedAssertion, VerifyError> {
verify_assertion(payload, expected_challenge, resolver, config).await
}
pub async fn enumerate_passkey_vms(
resolver: &VtaVmResolver,
did: &str,
) -> Result<Vec<PasskeyVmRef>, ResolverError> {
let _ = resolver;
let _ = did;
Ok(Vec::new())
}
pub struct PasskeyVmRef {
pub vm_url: String,
pub credential_id: Vec<u8>,
}
#[cfg(test)]
mod tests {
use super::*;
use affinidi_did_resolver_cache_sdk::config::DIDCacheConfigBuilder;
use vti_webauthn::VerificationAlgorithm;
async fn make_resolver() -> VtaVmResolver {
let client = DIDCacheClient::new(DIDCacheConfigBuilder::default().build())
.await
.expect("did cache client");
VtaVmResolver::new(client)
}
#[tokio::test]
async fn rejects_vm_url_without_fragment() {
let resolver = make_resolver().await;
let err = resolver.resolve_vm("did:example:alice").await.unwrap_err();
assert!(
matches!(err, ResolverError::MalformedVm(ref s) if s.contains("fragment")),
"got {err:?}"
);
}
#[tokio::test]
async fn surfaces_unresolvable_did() {
let resolver = make_resolver().await;
let err = resolver
.resolve_vm("did:web:nonexistent.invalid#passkey-abc")
.await
.unwrap_err();
assert!(
matches!(
err,
ResolverError::UnresolvableDid(_) | ResolverError::MalformedVm(_)
),
"got {err:?}"
);
}
#[tokio::test]
async fn rejects_did_key_without_a_passkey_vm() {
let resolver = make_resolver().await;
let err = resolver
.resolve_vm(
"did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#nonexistent-passkey",
)
.await
.unwrap_err();
assert!(matches!(err, ResolverError::NotFound), "got {err:?}");
}
#[tokio::test]
async fn resolves_did_key_default_vm() {
let resolver = make_resolver().await;
let did = "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH";
let vm_url = format!("{did}#{}", &did[8..]);
let err = resolver.resolve_vm(&vm_url).await.unwrap_err();
assert!(
matches!(err, ResolverError::MalformedVm(ref s) if s.contains("unsupported multicodec")),
"got {err:?}"
);
}
#[allow(clippy::needless_borrow)]
#[tokio::test]
async fn implements_vti_webauthn_resolver_trait() {
let resolver = make_resolver().await;
let _: &dyn VmResolver = &resolver;
let _ = VerificationAlgorithm::P256;
}
}