use serde_json::Value;
use trust_tasks_rs::specs::auth::whoami::v0_1 as whoami;
use trust_tasks_rs::{Payload, RejectReason, StandardCode, TrustTask, TrustTaskCode};
use vti_common::auth::session::{Session, SessionState, list_sessions, now_epoch};
use super::helpers::{
TrustTaskOutcome, app_error_to_reject, reject_with, reject_with_code, success_response,
};
use super::{JoinAuthCtx, parse_spec_payload};
use crate::server::AppState;
pub(crate) const WHOAMI_TYPE: &str = <whoami::Payload as Payload>::TYPE_URI;
pub(crate) const URIS: &[&str] = &[WHOAMI_TYPE];
pub(super) async fn dispatch(
state: &AppState,
ctx: &JoinAuthCtx,
doc: TrustTask<Value>,
type_uri: &str,
) -> Option<TrustTaskOutcome> {
if type_uri != WHOAMI_TYPE {
return None;
}
Some(handle(state, ctx, doc).await)
}
fn refused(doc: &TrustTask<Value>, message: &str) -> TrustTaskOutcome {
reject_with_code(
doc,
TrustTaskCode::Standard(StandardCode::PermissionDenied),
message,
None,
)
}
async fn handle(state: &AppState, ctx: &JoinAuthCtx, doc: TrustTask<Value>) -> TrustTaskOutcome {
let Some(signer) = ctx.verified_signer.clone() else {
return reject_with(&doc, RejectReason::ProofRequired);
};
if let Err(reject) = parse_spec_payload::<whoami::Payload>(&doc) {
return reject;
}
let session = match session_for_signer(state, &signer).await {
Ok(Some(s)) => s,
Ok(None) => {
return refused(
&doc,
"the signer is not the key of a live session, nor a console key of one",
);
}
Err(e) => return app_error_to_reject(&doc, &e),
};
let role = match crate::acl::resolve_auth_role(&state.acl_ks, &session.did).await {
Ok((role, _)) => role,
Err(e) => return app_error_to_reject(&doc, &e),
};
let response = crate::routes::auth::whoami_for(state, &session, role).await;
success_response(&doc, response)
}
async fn session_for_signer(
state: &AppState,
signer: &str,
) -> Result<Option<Session>, crate::error::AppError> {
let now = now_epoch();
let idle_ttl = state.config.read().await.auth.admin_idle_timeout;
let live: Vec<Session> = list_sessions(&state.sessions_ks)
.await?
.into_iter()
.filter(|s| is_live(s, now, idle_ttl))
.collect();
let newest = |it: &mut dyn Iterator<Item = Session>| it.max_by_key(|s| s.created_at);
if let Some(multikey) = crate::member_portal::oob::did_key_multikey(signer)
&& let Some(s) = newest(
&mut live
.iter()
.filter(|s| s.session_pubkey_b58btc.as_deref() == Some(multikey))
.cloned(),
)
{
return Ok(Some(s));
}
if crate::acl::get_acl_entry(&state.acl_ks, signer)
.await?
.is_some()
{
return Ok(None);
}
let Some(delegation) =
crate::acl::console_key::resolve_delegated_admin(&state.console_keys_ks, signer).await?
else {
return Ok(None);
};
let found = newest(&mut live.into_iter().filter(|s| s.did == delegation.admin_did));
if found.is_some() {
crate::acl::console_key::touch_last_used(&state.console_keys_ks, &delegation).await;
}
Ok(found)
}
fn is_live(s: &Session, now: u64, idle_ttl: u64) -> bool {
if s.state != SessionState::Authenticated {
return false;
}
let Some(refresh_end) = s.refresh_expires_at else {
return false;
};
if now >= refresh_end || s.past_end(now) {
return false;
}
let last_activity = if s.last_seen == 0 {
s.created_at
} else {
s.last_seen
};
now.saturating_sub(last_activity) <= idle_ttl
}