use axum::extract::State;
use axum::response::Response;
use serde_json::Value;
use trust_tasks_rs::TrustTask;
use crate::auth::AuthClaims;
use crate::error::AppError;
use crate::server::AppState;
mod acl;
mod audit;
mod auth;
mod backup;
mod config;
mod contexts;
mod did_templates;
mod discovery;
mod helpers;
mod keys;
mod management;
#[cfg(all(feature = "webvh", feature = "didcomm"))]
mod passkey_vms;
#[cfg(feature = "webvh")]
mod provision_integration;
mod seeds;
mod step_up;
pub(crate) use step_up::RequireStepUp;
mod vault;
#[cfg(feature = "webvh")]
mod webvh;
use helpers::{body_parse_error_response, method_not_found, reject_with};
#[cfg(feature = "didcomm")]
use trust_tasks_rs::RejectReason;
#[allow(dead_code)] const REST_ROUTED: &[&str] = &[
vta_sdk::trust_tasks::TASK_AUTH_CHALLENGE_0_1,
vta_sdk::trust_tasks::TASK_AUTH_AUTHENTICATE_0_1,
vta_sdk::trust_tasks::TASK_AUTH_REFRESH_0_1,
vta_sdk::trust_tasks::TASK_AUTH_PASSKEY_LOGIN_START_0_1,
vta_sdk::trust_tasks::TASK_AUTH_PASSKEY_LOGIN_FINISH_0_1,
vta_sdk::trust_tasks::TASK_ATTESTATION_STATUS_1_0,
vta_sdk::trust_tasks::TASK_ATTESTATION_REPORT_1_0,
];
#[allow(dead_code)] const KNOWN_FEATURE_GATED_URIS: &[&str] = &[
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_ENROLL_CHALLENGE_1_0,
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_ENROLL_SUBMIT_1_0,
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_LIST_1_0,
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_REVOKE_1_0,
vta_sdk::trust_tasks::TASK_PROVISION_INTEGRATION_REQUEST_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_SERVERS_LIST_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_SERVERS_ADD_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_SERVERS_UPDATE_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_SERVERS_REMOVE_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_LIST_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_CREATE_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_GET_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_GET_LOG_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_DELETE_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_UPDATE_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_ROTATE_KEYS_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_REGISTER_WITH_SERVER_1_0,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_REGISTER_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_PUBLISH_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_DELETE_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_ENABLE_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_DISABLE_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_LIST_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_INFO_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_CHECK_NAME_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_CHANGE_OWNER_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_ROLLBACK_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_PROBLEM_REPORT_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DOMAIN_CREATE_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DOMAIN_UPDATE_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DOMAIN_DISABLE_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DOMAIN_PURGE_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DOMAIN_SET_DEFAULT_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DOMAIN_ASSIGN_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DOMAIN_UNASSIGN_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_SERVER_REGISTER_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_SERVER_HEALTH_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_SERVER_STATS_SYNC_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_REGISTRY_ADMIN_REGISTER_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_REGISTRY_DEREGISTER_0_1,
];
#[cfg(test)]
fn aggregate_dispatched_uris() -> Vec<&'static str> {
let mut v: Vec<&'static str> = Vec::new();
v.extend(acl::DISPATCHED_URIS);
v.extend(audit::DISPATCHED_URIS);
v.extend(auth::DISPATCHED_URIS);
v.extend(backup::DISPATCHED_URIS);
v.extend(config::DISPATCHED_URIS);
v.extend(contexts::DISPATCHED_URIS);
v.extend(did_templates::DISPATCHED_URIS);
v.extend(discovery::DISPATCHED_URIS);
v.extend(keys::DISPATCHED_URIS);
v.extend(management::DISPATCHED_URIS);
v.extend(seeds::DISPATCHED_URIS);
v.extend(step_up::DISPATCHED_URIS);
v.extend(vault::DISPATCHED_URIS);
#[cfg(all(feature = "webvh", feature = "didcomm"))]
v.extend(passkey_vms::DISPATCHED_URIS);
#[cfg(feature = "webvh")]
v.extend(provision_integration::DISPATCHED_URIS);
#[cfg(feature = "webvh")]
v.extend(webvh::DISPATCHED_URIS);
v
}
pub async fn dispatch_trust_task(
auth: AuthClaims,
State(state): State<AppState>,
body: axum::body::Bytes,
) -> Result<Response, AppError> {
Ok(dispatch_trust_task_core(&state, &auth, &body).await)
}
pub(crate) async fn dispatch_trust_task_core(
state: &AppState,
auth: &AuthClaims,
body: &[u8],
) -> Response {
let doc: TrustTask<Value> = match serde_json::from_slice(body) {
Ok(d) => d,
Err(e) => return body_parse_error_response(&e.to_string()),
};
{
let vta_did = state.config.read().await.vta_did.clone();
if let Some(my_vid) = vta_did.as_deref()
&& let Err(reason) = doc.validate_basic(chrono::Utc::now(), my_vid)
{
return reject_with(&doc, reason);
}
}
let _ = auth;
dispatch_typed(state, auth, doc).await
}
#[cfg(feature = "didcomm")]
pub(crate) fn reject_trust_task(body: &[u8], reason: RejectReason) -> Response {
match serde_json::from_slice::<TrustTask<Value>>(body) {
Ok(doc) => reject_with(&doc, reason),
Err(e) => body_parse_error_response(&e.to_string()),
}
}
async fn dispatch_typed(state: &AppState, auth: &AuthClaims, doc: TrustTask<Value>) -> Response {
let type_uri = doc.type_uri.to_string();
match type_uri.as_str() {
vta_sdk::trust_tasks::TASK_AUTH_REVOKE_SESSION_0_1 => {
auth::handle_revoke_session(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_AUTH_WHOAMI_0_1 => auth::handle_whoami(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_AUTH_SESSIONS_LIST_0_1 => {
auth::handle_sessions_list(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_AUTH_STEP_UP_APPROVE_RESPONSE_0_1 => {
step_up::handle_approve_response(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_ACL_LIST_1_0 => acl::handle_list(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_ACL_CREATE_1_0 => acl::handle_create(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_ACL_GET_1_0 => acl::handle_get(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_ACL_UPDATE_1_0 => acl::handle_update(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_ACL_DELETE_1_0 => acl::handle_delete(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_CONTEXTS_LIST_1_0 => {
contexts::handle_list(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_CONTEXTS_CREATE_1_0 => {
contexts::handle_create(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_CONTEXTS_GET_1_0 => contexts::handle_get(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_CONTEXTS_UPDATE_1_0 => {
contexts::handle_update(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_CONTEXTS_UPDATE_DID_1_0 => {
contexts::handle_update_did(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_CONTEXTS_PREVIEW_DELETE_1_0 => {
contexts::handle_preview_delete(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_CONTEXTS_DELETE_1_0 => {
contexts::handle_delete(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_KEYS_LIST_1_0 => keys::handle_list(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_KEYS_CREATE_1_0 => keys::handle_create(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_KEYS_GET_1_0 => keys::handle_get(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_KEYS_RENAME_1_0 => keys::handle_rename(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_KEYS_REVOKE_1_0 => keys::handle_revoke(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_KEYS_SIGN_1_0 => keys::handle_sign(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_SEEDS_LIST_1_0 => seeds::handle_list(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_SEEDS_ROTATE_1_0 => seeds::handle_rotate(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_SEEDS_EXPORT_MNEMONIC_1_0 => {
seeds::handle_export_mnemonic(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_AUDIT_LIST_LOGS_1_0 => {
audit::handle_list_logs(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_AUDIT_GET_RETENTION_1_0 => {
audit::handle_get_retention(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_AUDIT_UPDATE_RETENTION_1_0 => {
audit::handle_update_retention(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_DISCOVERY_CAPABILITIES_1_0 => {
discovery::handle_capabilities(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_VAULT_LIST_0_1 => vault::handle_list(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_VAULT_GET_0_1 => vault::handle_get(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_VAULT_UPSERT_0_1 => vault::handle_upsert(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_VAULT_DELETE_0_1 => vault::handle_delete(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_VAULT_RELEASE_0_1 => {
vault::handle_release(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_VAULT_PROXY_LOGIN_0_1 => {
vault::handle_proxy_login(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_VAULT_SIGN_TRUST_TASK_0_1 => {
vault::handle_sign_trust_task(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_CONFIG_GET_1_0 => config::handle_get(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_CONFIG_UPDATE_1_0 => {
config::handle_update(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_MANAGEMENT_RELOAD_SERVICES_1_0 => {
management::handle_reload_services(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_BACKUP_INITIATE_EXPORT_1_0 => {
backup::handle_initiate_export(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_BACKUP_COMPLETE_EXPORT_1_0 => {
backup::handle_complete_export(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_BACKUP_INITIATE_IMPORT_1_0 => {
backup::handle_initiate_import(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_BACKUP_FINALIZE_IMPORT_1_0 => {
backup::handle_finalize_import(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_BACKUP_ABORT_1_0 => backup::handle_abort(state, auth, doc).await,
vta_sdk::trust_tasks::TASK_DID_TEMPLATES_LIST_1_0 => {
did_templates::handle_list(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_DID_TEMPLATES_CREATE_1_0 => {
did_templates::handle_create(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_DID_TEMPLATES_GET_1_0 => {
did_templates::handle_get(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_DID_TEMPLATES_UPDATE_1_0 => {
did_templates::handle_update(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_DID_TEMPLATES_DELETE_1_0 => {
did_templates::handle_delete(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_DID_TEMPLATES_RENDER_1_0 => {
did_templates::handle_render(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_CONTEXTS_DID_TEMPLATES_LIST_1_0 => {
did_templates::handle_context_list(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_CONTEXTS_DID_TEMPLATES_CREATE_1_0 => {
did_templates::handle_context_create(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_CONTEXTS_DID_TEMPLATES_GET_1_0 => {
did_templates::handle_context_get(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_CONTEXTS_DID_TEMPLATES_UPDATE_1_0 => {
did_templates::handle_context_update(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_CONTEXTS_DID_TEMPLATES_DELETE_1_0 => {
did_templates::handle_context_delete(state, auth, doc).await
}
vta_sdk::trust_tasks::TASK_CONTEXTS_DID_TEMPLATES_RENDER_1_0 => {
did_templates::handle_context_render(state, auth, doc).await
}
#[cfg(all(feature = "webvh", feature = "didcomm"))]
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_ENROLL_CHALLENGE_1_0 => {
passkey_vms::handle_enroll_challenge(state, auth, doc).await
}
#[cfg(all(feature = "webvh", feature = "didcomm"))]
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_ENROLL_SUBMIT_1_0 => {
passkey_vms::handle_enroll_submit(state, auth, doc).await
}
#[cfg(all(feature = "webvh", feature = "didcomm"))]
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_LIST_1_0 => {
passkey_vms::handle_list(state, auth, doc).await
}
#[cfg(all(feature = "webvh", feature = "didcomm"))]
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_REVOKE_1_0 => {
passkey_vms::handle_revoke(state, auth, doc).await
}
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_PROVISION_INTEGRATION_REQUEST_1_0 => {
provision_integration::handle_request(state, auth, doc).await
}
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_SERVERS_LIST_1_0 => {
webvh::handle_servers_list(state, auth, doc).await
}
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_SERVERS_ADD_1_0 => {
webvh::handle_servers_add(state, auth, doc).await
}
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_SERVERS_UPDATE_1_0 => {
webvh::handle_servers_update(state, auth, doc).await
}
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_SERVERS_REMOVE_1_0 => {
webvh::handle_servers_remove(state, auth, doc).await
}
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_LIST_1_0 => {
webvh::handle_dids_list(state, auth, doc).await
}
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_CREATE_1_0 => {
webvh::handle_dids_create(state, auth, doc).await
}
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_GET_1_0 => {
webvh::handle_dids_get(state, auth, doc).await
}
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_GET_LOG_1_0 => {
webvh::handle_dids_get_log(state, auth, doc).await
}
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_DELETE_1_0 => {
webvh::handle_dids_delete(state, auth, doc).await
}
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_UPDATE_1_0 => {
webvh::handle_dids_update(state, auth, doc).await
}
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_ROTATE_KEYS_1_0 => {
webvh::handle_dids_rotate_keys(state, auth, doc).await
}
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_REGISTER_WITH_SERVER_1_0 => {
webvh::handle_dids_register_with_server(state, auth, doc).await
}
_ => method_not_found(doc, &type_uri),
}
}
#[cfg(test)]
mod tests {
use trust_tasks_rs::TrustTask;
use super::*;
#[test]
fn body_parse_error_wire_shape() {
let resp = body_parse_error_response("expected `,`");
let _ = resp;
}
#[test]
fn framework_requires_canonical_uri_in_wire_type_field() {
let canonical = serde_json::json!({
"id": "urn:uuid:00000000-0000-0000-0000-000000000001",
"type": "https://trusttasks.org/spec/auth/revoke-session/0.1",
"issuer": "did:example:alice",
"recipient": "did:example:vta",
"issuedAt": "2026-05-20T00:00:00Z",
"payload": { "session_id": "sess-1" }
});
let bytes = serde_json::to_vec(&canonical).unwrap();
let parsed: Result<TrustTask<Value>, _> = serde_json::from_slice(&bytes);
assert!(
parsed.is_ok(),
"canonical URI must parse: {:?}",
parsed.err()
);
let flat = serde_json::json!({
"id": "urn:uuid:00000000-0000-0000-0000-000000000001",
"type": "https://trusttasks.org/vta/auth/revoke-session/1.0",
"issuer": "did:example:alice",
"recipient": "did:example:vta",
"issuedAt": "2026-05-20T00:00:00Z",
"payload": { "session_id": "sess-1" }
});
let bytes = serde_json::to_vec(&flat).unwrap();
let parsed: Result<TrustTask<Value>, _> = serde_json::from_slice(&bytes);
assert!(
parsed.is_err(),
"flat URI must NOT parse — if this changes, the framework \
relaxed its parser and Phase 3 design can simplify"
);
}
#[test]
fn phase_2_uri_registry_present() {
let _ = vta_sdk::trust_tasks::TASK_AUTH_CHALLENGE_0_1;
let _ = vta_sdk::trust_tasks::TASK_AUTH_AUTHENTICATE_0_1;
let _ = vta_sdk::trust_tasks::TASK_AUTH_REFRESH_0_1;
let _ = vta_sdk::trust_tasks::TASK_AUTH_REVOKE_SESSION_0_1;
let _ = vta_sdk::trust_tasks::TASK_AUTH_WHOAMI_0_1;
let _ = vta_sdk::trust_tasks::TASK_AUTH_SESSIONS_LIST_0_1;
let _ = vta_sdk::trust_tasks::TASK_AUTH_PASSKEY_LOGIN_START_0_1;
let _ = vta_sdk::trust_tasks::TASK_AUTH_PASSKEY_LOGIN_FINISH_0_1;
}
#[test]
fn dispatcher_handles_every_vta_sdk_uri() {
let dispatched = aggregate_dispatched_uris();
for declared in vta_sdk::trust_tasks::ALL_URIS {
let in_dispatched = dispatched.contains(declared);
let in_rest_routed = REST_ROUTED.contains(declared);
let in_feature_gated = KNOWN_FEATURE_GATED_URIS.contains(declared);
assert!(
in_dispatched || in_rest_routed || in_feature_gated,
"vta-sdk declares URI `{declared}` but it is not tracked in this dispatcher — \
either (a) add it to a slice's `DISPATCHED_URIS` const and wire a match arm, \
(b) add it to `REST_ROUTED` if it lives on a dedicated REST route, or \
(c) add it to `KNOWN_FEATURE_GATED_URIS` with a comment explaining the gating"
);
}
}
#[test]
fn no_uri_is_both_dispatched_and_rest_routed() {
let dispatched = aggregate_dispatched_uris();
for uri in REST_ROUTED {
assert!(
!dispatched.contains(uri),
"URI `{uri}` is in REST_ROUTED but also in a slice's DISPATCHED_URIS — \
a URI must live on exactly one transport"
);
}
}
}