use thiserror::Error;
use crate::auth::AuthClaims;
use crate::error::AppError;
use crate::operations::did_webvh::UpdateDidWebvhError;
use crate::operations::protocol::document::DocumentPatchError;
use crate::operations::protocol::service_lifecycle::{
EnableMutationError, ServiceMutationError, WebauthnService, run_enable,
};
use crate::operations::protocol::{OpContext, ServiceOpDeps};
#[derive(Debug, Clone)]
pub struct EnableWebauthnParams {
pub url: String,
}
#[derive(Debug, Clone)]
pub struct EnableWebauthnResult {
pub new_version_id: String,
pub url: String,
pub vta_did: String,
pub serverless: bool,
}
#[derive(Debug, Error)]
pub enum EnableWebauthnError {
#[error(
"WebAuthn is already enabled. Use `services webauthn update --url <url>` to change the URL."
)]
ServiceAlreadyEnabled,
#[error("invalid URL: {0}")]
Validation(String),
#[error("VTA DID is not configured — run `vta setup` first")]
VtaDidNotConfigured,
#[error("VTA DID `{0}` has no webvh record")]
VtaDidRecordMissing(String),
#[error("VTA DID `{0}` has no published log")]
VtaDidLogMissing(String),
#[error("VTA DID log is empty")]
EmptyLog,
#[error("DID document patch failed: {0}")]
DocumentPatch(#[from] DocumentPatchError),
#[error("WebVH update failed: {0}")]
WebVHUpdate(#[from] UpdateDidWebvhError),
#[error("config persistence failed: {0}")]
ConfigPersistence(String),
#[error("auth: {0}")]
Auth(String),
#[error("storage error: {0}")]
Storage(String),
}
impl From<AppError> for EnableWebauthnError {
fn from(value: AppError) -> Self {
Self::Storage(value.to_string())
}
}
impl From<crate::operations::protocol::preconditions::ProtocolPreconditionError>
for EnableWebauthnError
{
fn from(value: crate::operations::protocol::preconditions::ProtocolPreconditionError) -> Self {
use crate::operations::protocol::preconditions::ProtocolPreconditionError as E;
match value {
E::VtaDidNotConfigured => Self::VtaDidNotConfigured,
E::VtaDidRecordMissing(s) => Self::VtaDidRecordMissing(s),
E::VtaDidLogMissing(s) => Self::VtaDidLogMissing(s),
E::EmptyLog => Self::EmptyLog,
E::Storage(s) | E::DocumentParse(s) => Self::Storage(s),
}
}
}
impl ServiceMutationError for EnableWebauthnError {
fn validation(msg: String) -> Self {
Self::Validation(msg)
}
fn auth(msg: String) -> Self {
Self::Auth(msg)
}
fn storage(msg: String) -> Self {
Self::Storage(msg)
}
}
impl EnableMutationError for EnableWebauthnError {
fn already_enabled() -> Self {
Self::ServiceAlreadyEnabled
}
fn config_persistence(msg: String) -> Self {
Self::ConfigPersistence(msg)
}
}
pub async fn enable_webauthn(
deps: &ServiceOpDeps<'_>,
auth: &AuthClaims,
params: EnableWebauthnParams,
ctx: OpContext,
channel: &str,
) -> Result<EnableWebauthnResult, EnableWebauthnError> {
let ok = run_enable::<WebauthnService, EnableWebauthnError>(
deps,
auth,
¶ms.url,
ctx,
channel,
|| async {
let (contents, path) = {
let mut cfg = deps.config.write().await;
cfg.services.webauthn = true;
let contents = toml::to_string_pretty(&*cfg).map_err(|e| e.to_string())?;
let path = cfg.config_path.clone();
(contents, path)
};
std::fs::write(&path, contents).map_err(|e| e.to_string())?;
Ok(())
},
)
.await?;
Ok(EnableWebauthnResult {
new_version_id: ok.new_version_id,
url: ok.canonical_url,
vta_did: ok.vta_did,
serverless: ok.serverless,
})
}