use crate::client::SecretVerifier;
use crate::error::ErrorCode;
use crate::grant::GrantType;
use crate::registration::RegistrationPolicy;
use crate::scope::ScopeSet;
use crate::token::TokenTypeHint;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ClientAuthFailure {
UnknownClient,
SecretMismatch,
RateLimited,
SecretExpired,
#[cfg(feature = "mtls")]
NoCertificatePresented,
#[cfg(feature = "mtls")]
CertificateMismatch,
#[cfg(feature = "client_assertion")]
AssertionInvalid,
}
impl std::fmt::Display for ClientAuthFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
ClientAuthFailure::UnknownClient => "no registration for that client_id",
ClientAuthFailure::SecretMismatch => "the presented client credential did not verify",
ClientAuthFailure::RateLimited => "the host's rate limiter refused the attempt",
ClientAuthFailure::SecretExpired => {
"the registration's client_secret_expires_at has passed"
}
#[cfg(feature = "mtls")]
ClientAuthFailure::NoCertificatePresented => {
"the registration authenticates with mutual TLS and no certificate was presented"
}
#[cfg(feature = "mtls")]
ClientAuthFailure::CertificateMismatch => {
"the presented certificate is not one this registration authenticates with"
}
#[cfg(feature = "client_assertion")]
ClientAuthFailure::AssertionInvalid => "the client assertion did not verify",
})
}
}
impl std::error::Error for ClientAuthFailure {}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Event<'a> {
ClientAuthenticationFailed {
client_id: &'a str,
failure: ClientAuthFailure,
},
TokenIssued {
client_id: &'a str,
grant_type: GrantType,
subject: Option<&'a str>,
scope: &'a ScopeSet,
family_id: Option<&'a str>,
refresh_issued: bool,
},
GrantRefused {
client_id: &'a str,
grant_type: GrantType,
error: ErrorCode,
},
DeviceGrantApproved {
client_id: &'a str,
subject: &'a str,
},
DeviceGrantDenied {
client_id: &'a str,
},
AuthorizationCodeReplayDetected {
client_id: &'a str,
family_id: Option<&'a str>,
tokens_revoked: bool,
containment_failed: bool,
},
RefreshTokenReuseDetected {
client_id: &'a str,
family_id: &'a str,
records_revoked: u64,
},
TokenRevoked {
client_id: &'a str,
token_type: TokenTypeHint,
cascade_failed: bool,
},
#[cfg(feature = "consent")]
ConsentWithdrawn {
client_id: &'a str,
subject: &'a str,
records_revoked: u64,
},
ClientRegistered {
client_id: &'a str,
},
ClientRegistrationUpdated {
client_id: &'a str,
},
ClientRegistrationDeleted {
client_id: &'a str,
},
}
pub trait EventSink: Send + Sync {
fn on_event(&self, event: Event<'_>);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Attempt<'a> {
ClientAuthentication {
client_id: &'a str,
},
DeviceUserCodeEntry,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RateLimitDecision {
Allow,
Deny,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AttemptOutcome {
Succeeded,
Failed,
}
pub trait RateLimiter: Send + Sync {
fn check(&self, attempt: Attempt<'_>) -> RateLimitDecision;
fn record(&self, attempt: Attempt<'_>, outcome: AttemptOutcome) {
let _ = (attempt, outcome);
}
}
#[derive(Default)]
struct Installed {
events: Option<Box<dyn EventSink>>,
rate_limiter: Option<Box<dyn RateLimiter>>,
secret_verifier: Option<Box<dyn SecretVerifier>>,
registration_policy: Option<Box<dyn RegistrationPolicy>>,
#[cfg(feature = "jar")]
request_object_keys: Option<Box<dyn crate::par::RequestObjectKeys>>,
#[cfg(feature = "jwt")]
es256_verifier: Option<std::sync::Arc<dyn crate::jwt::Es256Verifier>>,
}
#[derive(Default)]
pub struct Hooks(Option<Box<Installed>>);
impl Hooks {
pub fn new() -> Self {
Hooks(None)
}
fn installed(&mut self) -> &mut Installed {
self.0.get_or_insert_with(Default::default)
}
pub fn install_event_sink(&mut self, sink: Box<dyn EventSink>) {
self.installed().events = Some(sink);
}
pub fn install_rate_limiter(&mut self, limiter: Box<dyn RateLimiter>) {
self.installed().rate_limiter = Some(limiter);
}
pub fn install_secret_verifier(&mut self, verifier: Box<dyn SecretVerifier>) {
self.installed().secret_verifier = Some(verifier);
}
pub fn install_registration_policy(&mut self, policy: Box<dyn RegistrationPolicy>) {
self.installed().registration_policy = Some(policy);
}
#[cfg(feature = "jar")]
pub fn install_request_object_keys(&mut self, keys: Box<dyn crate::par::RequestObjectKeys>) {
self.installed().request_object_keys = Some(keys);
}
#[cfg(feature = "jwt")]
pub fn install_es256_verifier(
&mut self,
verifier: std::sync::Arc<dyn crate::jwt::Es256Verifier>,
) {
self.installed().es256_verifier = Some(verifier);
}
#[cfg(feature = "jwt")]
pub fn es256_verifier(&self) -> Option<&std::sync::Arc<dyn crate::jwt::Es256Verifier>> {
match &self.0 {
Some(installed) => installed.es256_verifier.as_ref(),
None => None,
}
}
#[cfg(feature = "jar")]
pub fn request_object_keys(&self) -> Option<&dyn crate::par::RequestObjectKeys> {
match &self.0 {
Some(installed) => installed.request_object_keys.as_deref(),
None => None,
}
}
pub fn is_observed(&self) -> bool {
match &self.0 {
Some(installed) => installed.events.is_some(),
None => false,
}
}
pub fn emit<'a, F>(&self, event: F)
where
F: FnOnce() -> Event<'a>,
{
if let Some(installed) = &self.0 {
if let Some(sink) = &installed.events {
sink.on_event(event());
}
}
}
pub fn check(&self, attempt: Attempt<'_>) -> RateLimitDecision {
match &self.0 {
Some(installed) => match &installed.rate_limiter {
Some(limiter) => limiter.check(attempt),
None => RateLimitDecision::Allow,
},
None => RateLimitDecision::Allow,
}
}
pub fn record(&self, attempt: Attempt<'_>, outcome: AttemptOutcome) {
if let Some(installed) = &self.0 {
if let Some(limiter) = &installed.rate_limiter {
limiter.record(attempt, outcome);
}
}
}
pub fn secret_verifier(&self) -> Option<&dyn SecretVerifier> {
match &self.0 {
Some(installed) => installed.secret_verifier.as_deref(),
None => None,
}
}
pub fn registration_policy(&self) -> Option<&dyn RegistrationPolicy> {
match &self.0 {
Some(installed) => installed.registration_policy.as_deref(),
None => None,
}
}
}
#[cfg(test)]
#[path = "tests/events.rs"]
mod tests;