use serde_json::Value;
use trust_tasks_rs::specs::vtc::admin::bootstrap::v0_1 as bootstrap;
use trust_tasks_rs::specs::vtc::install::claim::{
finish::v0_2 as claim_finish, finish::v0_3 as claim_finish_v0_3, start::v0_2 as claim_start,
start::v0_3 as claim_start_v0_3,
};
use trust_tasks_rs::{Payload, RejectReason, TrustTask};
use super::helpers::{TrustTaskOutcome, reject_with, success_response, task_error_to_reject};
use super::{JoinAuthCtx, parse_spec_payload};
use crate::routes::admin::bootstrap::BootstrapRequest;
use crate::routes::install::{ClaimFinishRequest, ClaimStartRequest};
use crate::server::AppState;
pub(crate) const CLAIM_START_TYPE: &str = <claim_start::Payload as Payload>::TYPE_URI;
pub(crate) const CLAIM_FINISH_TYPE: &str = <claim_finish::Payload as Payload>::TYPE_URI;
pub(crate) const BOOTSTRAP_TYPE: &str = <bootstrap::Payload as Payload>::TYPE_URI;
pub(crate) const CLAIM_START_V0_3_TYPE: &str = <claim_start_v0_3::Payload as Payload>::TYPE_URI;
pub(crate) const CLAIM_FINISH_V0_3_TYPE: &str = <claim_finish_v0_3::Payload as Payload>::TYPE_URI;
pub(crate) const URIS: &[&str] = &[
CLAIM_START_TYPE,
CLAIM_FINISH_TYPE,
CLAIM_START_V0_3_TYPE,
CLAIM_FINISH_V0_3_TYPE,
BOOTSTRAP_TYPE,
];
pub(super) async fn dispatch(
state: &AppState,
ctx: &JoinAuthCtx,
doc: TrustTask<Value>,
type_uri: &str,
) -> Option<TrustTaskOutcome> {
Some(match type_uri {
CLAIM_START_TYPE => handle_claim_start(state, doc).await,
CLAIM_FINISH_TYPE => handle_claim_finish(state, doc).await,
CLAIM_START_V0_3_TYPE => handle_claim_start_v0_3(state, doc).await,
CLAIM_FINISH_V0_3_TYPE => handle_claim_finish_v0_3(state, ctx, doc).await,
BOOTSTRAP_TYPE => handle_bootstrap(state, doc).await,
_ => return None,
})
}
fn approver_refusal(
doc: &TrustTask<Value>,
e: &crate::step_up_approver::ApproverTaskError,
) -> TrustTaskOutcome {
use crate::step_up_approver::ApproverTaskError;
match e {
ApproverTaskError::Refused {
code,
message,
details,
} => super::helpers::reject_with_code(
doc,
super::helpers::declared_code(code),
message,
details.clone(),
),
ApproverTaskError::StepUp(_) => super::helpers::app_error_to_reject(
doc,
&vti_common::error::AppError::Internal("an install claim takes no step-up".into()),
),
ApproverTaskError::App(e) => super::helpers::app_error_to_reject(doc, e),
}
}
async fn handle_claim_start_v0_3(state: &AppState, doc: TrustTask<Value>) -> TrustTaskOutcome {
let payload: claim_start_v0_3::Payload = match parse_spec_payload(&doc) {
Ok(p) => p,
Err(reject) => return reject,
};
match crate::step_up_approver::claim_start_v0_3(state, &payload).await {
Ok(resp) => success_response(&doc, resp),
Err(e) => approver_refusal(&doc, &e),
}
}
async fn handle_claim_finish_v0_3(
state: &AppState,
ctx: &JoinAuthCtx,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
let Some(signer) = ctx.verified_signer.clone() else {
return reject_with(&doc, RejectReason::ProofRequired);
};
let payload: claim_finish_v0_3::Payload = match parse_spec_payload(&doc) {
Ok(p) => p,
Err(reject) => return reject,
};
let statement = doc.payload.get("statement").cloned().unwrap_or(Value::Null);
match crate::step_up_approver::claim_finish_v0_3(state, &signer, &payload, &statement).await {
Ok(resp) => success_response(&doc, resp),
Err(e) => approver_refusal(&doc, &e),
}
}
pub(super) async fn refresh_founder_did(
state: &AppState,
doc: &TrustTask<Value>,
) -> Option<TrustTaskOutcome> {
let claim_id = doc.payload.get("claimId")?.as_str()?;
let issuer = doc.issuer.as_deref()?;
let admin_did = crate::step_up_approver::claim_admin_did(state, claim_id)
.await
.ok()
.flatten()?;
if admin_did != issuer || issuer.starts_with("did:key:") || issuer.starts_with("did:peer:") {
return None;
}
let unresolvable = || {
super::helpers::reject_with_code(
doc,
super::helpers::extended_code(claim_finish_v0_3::error_codes::DID_UNRESOLVABLE.code),
"the DID the install token names could not be resolved to its live document; nothing was consumed — try again",
None,
)
};
let Some(resolver) = state.did_resolver.as_ref() else {
return Some(unresolvable());
};
resolver.remove(issuer).await;
match resolver.resolve(issuer).await {
Ok(_) => None,
Err(e) => {
tracing::warn!(did = %issuer, error = %e, "install claim 0.3: founder DID unresolvable");
Some(unresolvable())
}
}
}
async fn handle_claim_start(state: &AppState, doc: TrustTask<Value>) -> TrustTaskOutcome {
let payload: claim_start::Payload = match parse_spec_payload(&doc) {
Ok(p) => p,
Err(reject) => return reject,
};
let req = ClaimStartRequest {
install_token: payload.install_token.to_string(),
claim_secret: payload.claim_secret.as_ref().map(|s| s.to_string()),
};
match crate::routes::install::claim_start(state, req).await {
Ok(resp) => success_response(&doc, resp),
Err(e) => task_error_to_reject(&doc, &e),
}
}
async fn handle_claim_finish(state: &AppState, doc: TrustTask<Value>) -> TrustTaskOutcome {
let payload: claim_finish::Payload = match parse_spec_payload(&doc) {
Ok(p) => p,
Err(reject) => return reject,
};
let webauthn_response = match serde_json::from_value(Value::Object(payload.webauthn_response)) {
Ok(r) => r,
Err(e) => {
return reject_with(
&doc,
RejectReason::MalformedRequest {
reason: format!("webauthnResponse: {e}"),
},
);
}
};
let req = ClaimFinishRequest {
install_token: payload.install_token.to_string(),
registration_id: payload.registration_id.to_string(),
webauthn_response,
};
match crate::routes::install::claim_finish(state, req).await {
Ok(resp) => success_response(&doc, resp),
Err(e) => task_error_to_reject(&doc, &e),
}
}
async fn handle_bootstrap(state: &AppState, doc: TrustTask<Value>) -> TrustTaskOutcome {
let payload: bootstrap::Payload = match parse_spec_payload(&doc) {
Ok(p) => p,
Err(reject) => return reject,
};
let req = BootstrapRequest {
setup_session_token: payload.setup_session_token.to_string(),
};
match crate::routes::admin::bootstrap::bootstrap(state, req).await {
Ok(resp) => success_response(&doc, resp),
Err(e) => task_error_to_reject(&doc, &e),
}
}