use super::helpers::TrustTaskOutcome;
use serde_json::{Value, json};
use trust_tasks_rs::{RejectReason, TrustTask};
use vta_sdk::protocols::auth::{RevokeSessionRequest, RevokeSessionResponse, epoch_to_rfc3339};
use crate::acl::{Role, check_acl_full};
use crate::audit::audit;
use crate::auth::AuthClaims;
use crate::auth::session::{SessionState, delete_session, get_session, list_sessions, now_epoch};
use crate::server::AppState;
use super::helpers::{app_error_to_reject, reject_with, success_response};
pub(super) async fn handle_revoke_session(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
let req: RevokeSessionRequest = match serde_json::from_value(doc.payload.clone()) {
Ok(r) => r,
Err(e) => {
return reject_with(
&doc,
RejectReason::MalformedRequest {
reason: format!("revoke-session payload parse: {e}"),
},
);
}
};
let session = match get_session(&state.sessions_ks, &req.session_id).await {
Ok(Some(s)) => s,
Ok(None) => {
return reject_with(
&doc,
RejectReason::TaskFailed {
reason: format!("session not found: {}", req.session_id),
details: None,
},
);
}
Err(e) => {
tracing::error!(error = %e, "session lookup failed in revoke-session");
return reject_with(
&doc,
RejectReason::InternalError {
reason: format!("session lookup: {e}"),
},
);
}
};
if session.did != auth.did && auth.role != Role::Admin {
tracing::warn!(
caller = %auth.did,
session_did = %session.did,
session_id = %req.session_id,
"revoke-session rejected: caller is not owner or admin"
);
return reject_with(
&doc,
RejectReason::PermissionDenied {
reason: "cannot revoke another user's session".to_string(),
},
);
}
if let Err(e) = delete_session(&state.sessions_ks, &req.session_id).await {
tracing::error!(error = %e, session_id = %req.session_id, "session delete failed");
return reject_with(
&doc,
RejectReason::InternalError {
reason: format!("session delete: {e}"),
},
);
}
audit!(
"session.revoke",
actor = &auth.did,
resource = &req.session_id,
outcome = "success"
);
tracing::info!(
caller = %auth.did,
session_id = %req.session_id,
"session revoked via trust-task"
);
success_response(&doc, RevokeSessionResponse::default())
}
pub(super) async fn handle_whoami(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
let session = match get_session(&state.sessions_ks, &auth.session_id).await {
Ok(Some(s)) => s,
Ok(None) => {
return reject_with(
&doc,
RejectReason::TaskFailed {
reason: format!("session not found: {}", auth.session_id),
details: None,
},
);
}
Err(e) => {
tracing::error!(error = %e, "session lookup failed in whoami");
return reject_with(
&doc,
RejectReason::InternalError {
reason: format!("session lookup: {e}"),
},
);
}
};
let (role, contexts) = match check_acl_full(&state.acl_ks, &auth.did).await {
Ok(rc) => rc,
Err(e) => return app_error_to_reject(&doc, e),
};
let mut session_info = json!({
"id": auth.session_id,
"subject": auth.did,
"issuedAt": epoch_to_rfc3339(session.created_at),
"expiresAt": epoch_to_rfc3339(auth.access_expires_at),
"amr": session.amr,
});
if !session.acr.is_empty() {
session_info["acr"] = Value::String(session.acr.clone());
}
let scopes: Vec<String> = contexts.iter().map(|c| format!("ctx:{c}")).collect();
let body = json!({
"session": session_info,
"roles": [role.to_string()],
"scopes": scopes,
});
audit!(
"auth.whoami",
actor = &auth.did,
resource = &auth.session_id,
outcome = "success"
);
success_response(&doc, body)
}
pub(super) async fn handle_sessions_list(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
let all = match list_sessions(&state.sessions_ks).await {
Ok(s) => s,
Err(e) => {
tracing::error!(error = %e, "session list failed in sessions/list");
return reject_with(
&doc,
RejectReason::InternalError {
reason: format!("session list: {e}"),
},
);
}
};
let now = now_epoch();
let sessions: Vec<Value> = all
.into_iter()
.filter(|s| {
s.did == auth.did
&& s.state == SessionState::Authenticated
&& s.refresh_expires_at.is_none_or(|exp| exp > now)
})
.map(|s| {
let expires_at = s.refresh_expires_at.unwrap_or(s.created_at);
let mut item = json!({
"id": s.session_id,
"subject": s.did,
"issuedAt": epoch_to_rfc3339(s.created_at),
"expiresAt": epoch_to_rfc3339(expires_at),
"amr": s.amr,
});
if !s.acr.is_empty() {
item["acr"] = Value::String(s.acr);
}
item
})
.collect();
audit!(
"auth.sessions-list",
actor = &auth.did,
resource = &auth.session_id,
outcome = "success"
);
success_response(&doc, json!({ "sessions": sessions }))
}