use auths_oidc_port::{JwtValidator, OidcValidationConfig};
use auths_verifier::IdentityDID;
use chrono::{DateTime, Duration, Utc};
use super::error::FederationError;
use super::types::{AttestationContent, IdpId, LifecycleClaim, Nonce};
#[derive(Debug, Clone)]
pub struct OidcAttestationRequest {
pub subject: IdentityDID,
pub claim: LifecycleClaim,
pub expected_nonce: Nonce,
pub ttl_secs: i64,
}
pub async fn verify_oidc_attestation(
validator: &dyn JwtValidator,
config: &OidcValidationConfig,
token: &str,
request: &OidcAttestationRequest,
now: DateTime<Utc>,
) -> Result<AttestationContent, FederationError> {
let claims = validator
.validate(token, config, now)
.await
.map_err(|e| FederationError::TokenInvalid(e.to_string()))?;
let token_nonce = claims
.get("nonce")
.and_then(|v| v.as_str())
.ok_or(FederationError::NonceMissing)?;
if token_nonce != request.expected_nonce.as_str() {
return Err(FederationError::NonceMismatch);
}
let issuer = claims
.get("iss")
.and_then(|v| v.as_str())
.ok_or(FederationError::IssuerMissing)?;
let idp = IdpId::new(issuer)?;
validate_claim_against_token(&request.claim, &claims)?;
Ok(AttestationContent {
subject: request.subject.clone(),
idp,
claim: request.claim.clone(),
nonce: request.expected_nonce.clone(),
expires_at: now + Duration::seconds(request.ttl_secs),
})
}
fn validate_claim_against_token(
claim: &LifecycleClaim,
claims: &serde_json::Value,
) -> Result<(), FederationError> {
if let LifecycleClaim::GroupMember(group) = claim {
let present = claims
.get("groups")
.and_then(|v| v.as_array())
.map(|arr| arr.iter().any(|g| g.as_str() == Some(group.as_str())))
.unwrap_or(false);
if !present {
return Err(FederationError::ClaimNotInToken(format!(
"group '{}' not present in token 'groups'",
group.as_str()
)));
}
}
Ok(())
}