use std::fmt;
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use async_trait::async_trait;
use rvoip_core_traits::identity::{IdentityAssurance, Jwk};
pub use rvoip_core_traits::identity::{
AuthenticatedPrincipal, AuthenticationMethod, BearerAuthError, PrincipalOwnershipKey,
};
pub const MAX_BEARER_TOKEN_ID_BYTES: usize = 512;
pub const MAX_BEARER_SUBJECT_BYTES: usize = 1_024;
pub const MAX_BEARER_ISSUER_BYTES: usize = 2_048;
pub const MAX_BEARER_TENANT_BYTES: usize = 512;
#[derive(Clone)]
pub struct ValidatedBearer {
pub principal: AuthenticatedPrincipal,
pub token_id: Option<String>,
pub issued_at: Option<SystemTime>,
}
impl ValidatedBearer {
pub fn new(
principal: AuthenticatedPrincipal,
token_id: Option<String>,
issued_at: Option<SystemTime>,
) -> Result<Self, BearerAuthError> {
let principal = ensure_principal_active(principal)?;
let token_id = validate_optional_token_id(token_id)?;
if let (Some(issued_at), Some(expires_at)) = (issued_at, principal.expires_at) {
if issued_at > SystemTime::from(expires_at) {
return Err(BearerAuthError::Invalid(
"bearer credential issued-at time is later than expiry".into(),
));
}
}
Ok(Self {
principal,
token_id,
issued_at,
})
}
}
impl fmt::Debug for ValidatedBearer {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ValidatedBearer")
.field("principal", &self.principal)
.field("token_id", &self.token_id.as_ref().map(|_| "<redacted>"))
.field("issued_at", &self.issued_at)
.finish()
}
}
pub fn ensure_principal_active(
principal: AuthenticatedPrincipal,
) -> Result<AuthenticatedPrincipal, BearerAuthError> {
validate_required_identifier("subject", &principal.subject, MAX_BEARER_SUBJECT_BYTES)?;
validate_optional_identifier(
"issuer",
principal.issuer.as_deref(),
MAX_BEARER_ISSUER_BYTES,
)?;
validate_optional_identifier(
"tenant",
principal.tenant.as_deref(),
MAX_BEARER_TENANT_BYTES,
)?;
if principal.is_expired() {
Err(BearerAuthError::Invalid(
"authenticated principal is expired".into(),
))
} else {
Ok(principal)
}
}
fn validate_required_identifier(
name: &str,
value: &str,
max_bytes: usize,
) -> Result<(), BearerAuthError> {
if value.trim().is_empty() {
return Err(BearerAuthError::Invalid(format!(
"authenticated principal {name} is empty"
)));
}
if value.len() > max_bytes {
return Err(BearerAuthError::Invalid(format!(
"authenticated principal {name} exceeds {max_bytes} bytes"
)));
}
if value.chars().any(char::is_control) {
return Err(BearerAuthError::Invalid(format!(
"authenticated principal {name} contains control characters"
)));
}
Ok(())
}
fn validate_optional_identifier(
name: &str,
value: Option<&str>,
max_bytes: usize,
) -> Result<(), BearerAuthError> {
if let Some(value) = value {
validate_required_identifier(name, value, max_bytes)?;
}
Ok(())
}
pub(crate) fn validate_optional_token_id(
token_id: Option<String>,
) -> Result<Option<String>, BearerAuthError> {
let Some(token_id) = token_id else {
return Ok(None);
};
if token_id.trim().is_empty() {
return Err(BearerAuthError::Invalid(
"bearer credential token id is empty".into(),
));
}
if token_id.len() > MAX_BEARER_TOKEN_ID_BYTES {
return Err(BearerAuthError::Invalid(format!(
"bearer credential token id exceeds {MAX_BEARER_TOKEN_ID_BYTES} bytes"
)));
}
if token_id.chars().any(char::is_control) {
return Err(BearerAuthError::Invalid(
"bearer credential token id contains control characters".into(),
));
}
Ok(Some(token_id))
}
pub(crate) fn unix_time_from_seconds(
seconds: u64,
field: &str,
) -> Result<SystemTime, BearerAuthError> {
UNIX_EPOCH
.checked_add(Duration::from_secs(seconds))
.ok_or_else(|| {
BearerAuthError::Invalid(format!(
"bearer credential {field} is outside the supported range"
))
})
}
#[async_trait]
pub trait BearerValidator: Send + Sync {
async fn validate(&self, token: &str) -> Result<IdentityAssurance, BearerAuthError>;
async fn validate_principal(
&self,
token: &str,
) -> Result<AuthenticatedPrincipal, BearerAuthError> {
let assurance = self.validate(token).await?;
if matches!(
assurance,
IdentityAssurance::Anonymous | IdentityAssurance::Identified { .. }
) {
return Err(BearerAuthError::Invalid(format!(
"bearer validator returned {} assurance without a unique principal identity",
assurance.kind()
)));
}
ensure_principal_active(AuthenticatedPrincipal::from_assurance(assurance))
}
async fn validate_credential(&self, token: &str) -> Result<ValidatedBearer, BearerAuthError> {
ValidatedBearer::new(self.validate_principal(token).await?, None, None)
}
}
pub fn bearer_stub() -> Arc<dyn BearerValidator> {
Arc::new(StubBearerValidator)
}
struct StubBearerValidator;
#[async_trait]
impl BearerValidator for StubBearerValidator {
async fn validate(&self, token: &str) -> Result<IdentityAssurance, BearerAuthError> {
if token.is_empty() {
return Err(BearerAuthError::Empty);
}
let ephemeral_key = Jwk(serde_json::json!({
"kty": "stub",
"kid": uuid::Uuid::new_v4().simple().to_string(),
}));
Ok(IdentityAssurance::Pseudonymous { ephemeral_key })
}
async fn validate_principal(
&self,
token: &str,
) -> Result<AuthenticatedPrincipal, BearerAuthError> {
let assurance = self.validate(token).await?;
let mut principal = AuthenticatedPrincipal::from_assurance_with_method(
assurance,
AuthenticationMethod::Bearer,
);
principal.scopes = vec!["*".into()];
principal.tenant = Some("development".into());
ensure_principal_active(principal)
}
}