use uuid::Uuid;
use vta_sdk::protocols::auth::{ChallengeResponse, epoch_to_rfc3339};
use crate::auth::AuthError;
use crate::auth::backend::{AuthAuditEvent, AuthBackend, ChallengeInput, SessionStore};
use crate::auth::session::{Session, SessionState, now_epoch};
pub async fn handle_challenge<B: AuthBackend>(
backend: &B,
input: ChallengeInput,
) -> Result<ChallengeResponse, B::Error> {
backend.validate_did(&input.did).await?;
backend.check_acl(&input.did).await?;
let limit = backend.max_pending_challenges_per_did();
if limit > 0 {
let pending = backend
.sessions()
.count_pending_challenges(&input.did)
.await
.map_err(|e| AuthError::Internal(format!("count_pending_challenges failed: {e:?}")))?;
if pending >= limit {
tracing::warn!(
did = %input.did,
pending,
limit,
"auth challenge rate limited per-DID"
);
return Err(AuthError::PendingChallengeLimitReached.into());
}
}
let mut bytes = [0u8; 32];
rand::fill(&mut bytes);
let challenge = hex::encode(bytes);
let session_id = Uuid::new_v4().to_string();
let attestation = backend.attest_challenge(&bytes).await?;
let created_at = now_epoch();
let session = Session {
session_id: session_id.clone(),
did: input.did,
challenge: challenge.clone(),
state: SessionState::ChallengeSent,
created_at,
refresh_token: None,
refresh_expires_at: None,
tee_attested: attestation.attested,
amr: Vec::new(),
acr: String::new(),
token_id: None,
session_pubkey_b58btc: input.session_pubkey_b58btc,
};
backend
.sessions()
.store_session(&session)
.await
.map_err(|e| AuthError::Internal(format!("store_session failed: {e:?}")))?;
backend.audit(AuthAuditEvent::ChallengeIssued {
did: &session.did,
session_id: &session_id,
});
Ok(ChallengeResponse {
challenge,
session_id,
expires_at: epoch_to_rfc3339(created_at.saturating_add(backend.challenge_ttl())),
tee_attestation: attestation.report,
})
}