use std::collections::BTreeMap;
use std::time::{Duration, SystemTime};
use async_trait::async_trait;
use rvoip_core_traits::identity::IdentityAssurance;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::sip_digest::DigestAlgorithm;
#[derive(Debug, Error)]
pub enum CredentialAuthError {
#[error("invalid credentials")]
Invalid,
#[error("credential provider unavailable: {0}")]
Unavailable(String),
#[error("credential policy rejected request: {0}")]
PolicyRejected(String),
}
#[async_trait]
pub trait PasswordVerifier: Send + Sync {
async fn verify_password(
&self,
username: &str,
password: &str,
) -> Result<IdentityAssurance, CredentialAuthError>;
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum DigestSecret {
PlaintextPassword(String),
Ha1(String),
}
#[async_trait]
pub trait DigestSecretProvider: Send + Sync {
async fn lookup_digest_secret(
&self,
username: &str,
realm: &str,
algorithm: DigestAlgorithm,
) -> Result<Option<DigestSecret>, CredentialAuthError>;
}
#[async_trait]
pub trait ApiKeyVerifier: Send + Sync {
async fn verify_api_key(&self, api_key: &str)
-> Result<IdentityAssurance, CredentialAuthError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenRevocationStatus {
Active,
Revoked,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenRevocationContext {
pub token_id: String,
pub subject: Option<String>,
pub issuer: Option<String>,
pub issued_at: Option<SystemTime>,
pub expires_at: Option<SystemTime>,
}
impl TokenRevocationContext {
pub fn new(token_id: impl Into<String>) -> Self {
Self {
token_id: token_id.into(),
subject: None,
issuer: None,
issued_at: None,
expires_at: None,
}
}
pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
self.subject = Some(subject.into());
self
}
pub fn with_issuer(mut self, issuer: impl Into<String>) -> Self {
self.issuer = Some(issuer.into());
self
}
pub fn with_times(
mut self,
issued_at: Option<SystemTime>,
expires_at: Option<SystemTime>,
) -> Self {
self.issued_at = issued_at;
self.expires_at = expires_at;
self
}
}
#[async_trait]
pub trait TokenRevocationChecker: Send + Sync {
async fn check_token(
&self,
context: &TokenRevocationContext,
) -> Result<TokenRevocationStatus, CredentialAuthError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DigestNonceStatus {
Active,
Expired,
Unknown,
}
#[async_trait]
pub trait DigestReplayStore: Send + Sync {
async fn record_nonce(
&self,
nonce: &str,
expires_at: SystemTime,
) -> Result<(), CredentialAuthError>;
async fn nonce_status(
&self,
nonce: &str,
now: SystemTime,
) -> Result<DigestNonceStatus, CredentialAuthError>;
async fn accept_nonce_count(
&self,
username: &str,
nonce: &str,
nonce_count: u32,
) -> Result<bool, CredentialAuthError>;
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AuthAuditScheme {
Digest,
Bearer,
Basic,
Aka,
ApiKey,
Password,
Token,
Other(String),
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AuthFailureReason {
MissingCredential,
MalformedCredential,
InvalidCredential,
UnsupportedScheme,
PolicyRejected,
TokenExpired,
TokenRevoked,
StaleNonce,
ReplayRejected,
ProviderUnavailable,
Other(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AuthAuditOutcome {
Success,
Failure(AuthFailureReason),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthAuditEvent {
pub scheme: AuthAuditScheme,
pub outcome: AuthAuditOutcome,
pub subject: Option<String>,
pub realm: Option<String>,
pub peer: Option<String>,
pub metadata: BTreeMap<String, String>,
}
impl AuthAuditEvent {
pub fn new(scheme: AuthAuditScheme, outcome: AuthAuditOutcome) -> Self {
Self {
scheme,
outcome,
subject: None,
realm: None,
peer: None,
metadata: BTreeMap::new(),
}
}
pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
self.subject = Some(subject.into());
self
}
pub fn with_realm(mut self, realm: impl Into<String>) -> Self {
self.realm = Some(realm.into());
self
}
pub fn with_peer(mut self, peer: impl Into<String>) -> Self {
self.peer = Some(peer.into());
self
}
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
}
#[async_trait]
pub trait AuthAuditSink: Send + Sync {
async fn record_auth_event(&self, event: AuthAuditEvent) -> Result<(), CredentialAuthError>;
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AuthRateLimitKind {
SipRegister,
SipRequest,
BasicPassword,
Password,
ApiKey,
BearerToken,
TokenIssuance,
Digest,
Other(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AuthRateLimitKey {
pub kind: AuthRateLimitKind,
pub subject: Option<String>,
pub realm: Option<String>,
pub peer: Option<String>,
}
impl AuthRateLimitKey {
pub fn new(kind: AuthRateLimitKind) -> Self {
Self {
kind,
subject: None,
realm: None,
peer: None,
}
}
pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
self.subject = Some(subject.into());
self
}
pub fn with_realm(mut self, realm: impl Into<String>) -> Self {
self.realm = Some(realm.into());
self
}
pub fn with_peer(mut self, peer: impl Into<String>) -> Self {
self.peer = Some(peer.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AuthRateLimitVerdict {
Allowed,
Denied {
retry_after: Option<Duration>,
},
}
#[async_trait]
pub trait AuthRateLimiter: Send + Sync {
async fn check_auth_attempt(
&self,
key: &AuthRateLimitKey,
) -> Result<AuthRateLimitVerdict, CredentialAuthError>;
async fn record_auth_result(
&self,
key: &AuthRateLimitKey,
outcome: &AuthAuditOutcome,
) -> Result<(), CredentialAuthError>;
}