use std::sync::Arc;
use fraiseql_core::security::SecurityContext;
use crate::{middleware::oidc_auth::SessionTokenClaims, token_revocation::TokenRevocationManager};
#[derive(Clone)]
pub struct StreamAuthGuard {
expires_at: Option<chrono::DateTime<chrono::Utc>>,
sub: Option<String>,
claims: Option<SessionTokenClaims>,
revocation: Option<Arc<TokenRevocationManager>>,
}
impl StreamAuthGuard {
#[must_use]
pub fn new(
principal: Option<&SecurityContext>,
claims: Option<SessionTokenClaims>,
revocation: Option<Arc<TokenRevocationManager>>,
) -> Self {
Self {
expires_at: principal.map(|p| p.expires_at),
sub: principal.map(|p| p.user_id.to_string()),
revocation: if claims.is_some() { revocation } else { None },
claims,
}
}
#[must_use]
pub const fn applies(&self) -> bool {
self.expires_at.is_some()
}
#[must_use]
pub fn expired(&self) -> bool {
self.expires_at.is_some_and(|exp| exp <= chrono::Utc::now())
}
pub async fn check(&self) -> Result<(), &'static str> {
if self.expired() {
return Err("Token expired");
}
if let (Some(revocation), Some(sub), Some(claims)) =
(self.revocation.as_ref(), self.sub.as_deref(), self.claims.as_ref())
{
use crate::token_revocation::TokenRejection;
match revocation.check_token(claims.jti.as_deref(), sub, claims.iat).await {
Ok(()) => {},
Err(TokenRejection::Revoked) => return Err("Token revoked"),
Err(TokenRejection::MissingJti) => return Err("Token lacks required jti claim"),
Err(TokenRejection::StoreUnavailable) => {
return Err("Revocation store unavailable");
},
}
}
Ok(())
}
}