use crate::token::TokenRejection;
#[cfg(feature = "tower")]
pub(crate) const REASON_NONE: &str = "none";
#[cfg(feature = "tower")]
pub(crate) const REASON_MISCONFIGURED: &str = "misconfigured";
#[allow(clippy::needless_borrows_for_generic_args)]
pub(crate) fn record_field<V: tracing::field::Value>(span: &tracing::Span, field: &str, value: V) {
span.record(field, &value);
}
#[cfg(feature = "tower")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Outcome {
Accepted,
Rejected,
PassedThrough,
}
#[cfg(feature = "tower")]
impl Outcome {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Accepted => "accepted",
Self::Rejected => "rejected",
Self::PassedThrough => "passed_through",
}
}
}
#[cfg(feature = "tower")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Mechanism {
Static,
OAuth,
None,
}
#[cfg(feature = "tower")]
impl Mechanism {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Static => "static",
Self::OAuth => "oauth",
Self::None => "none",
}
}
pub(crate) fn of_rejection(rejection: &TokenRejection) -> Self {
use crate::token::InvalidTokenKind as K;
match rejection {
TokenRejection::Missing => Self::None,
TokenRejection::Invalid(invalid) => match invalid.kind() {
K::StaticTokenMismatch | K::OAuthTokenRequired => Self::Static,
K::NoMechanism => Self::None,
_ => Self::OAuth,
},
_ => Self::OAuth,
}
}
pub(crate) fn of_request(
credential: Option<&crate::authenticate::Credential>,
rejection: &TokenRejection,
) -> Self {
use crate::authenticate::Credential;
match credential {
Some(Credential::OAuth(_)) => Self::OAuth,
Some(Credential::StaticToken) => Self::Static,
_ => Self::of_rejection(rejection),
}
}
}
pub(crate) fn reason(rejection: &TokenRejection) -> &'static str {
match rejection {
TokenRejection::Missing => "missing",
TokenRejection::Invalid(invalid) => invalid.kind().as_str(),
TokenRejection::InsufficientScope => "insufficient_scope",
#[allow(unreachable_patterns)]
_ => "other",
}
}
#[cfg(feature = "tower")]
pub(crate) fn status(rejection: &TokenRejection) -> u16 {
crate::refusal::select::<str>(rejection, None, None).0
}
#[cfg(feature = "tower")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Stage {
Layer,
Route,
Handler,
}
#[cfg(feature = "tower")]
impl Stage {
#[cfg_attr(not(feature = "metrics"), allow(dead_code))]
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Layer => "layer",
Self::Route => "route",
Self::Handler => "handler",
}
}
}
#[cfg(feature = "tower")]
#[inline]
pub(crate) fn count_request(
stage: Stage,
outcome: Outcome,
mechanism: Mechanism,
reason: &'static str,
) {
#[cfg(feature = "metrics")]
::metrics::counter!(
crate::observability::REQUESTS_TOTAL,
"stage" => stage.as_str(),
"outcome" => outcome.as_str(),
"mechanism" => mechanism.as_str(),
"reason" => reason,
)
.increment(1);
#[cfg(not(feature = "metrics"))]
let _ = (stage, outcome, mechanism, reason);
}
#[inline]
pub(crate) fn count_refresh(issuer_host: &str, result: &'static str, keys: usize) {
#[cfg(feature = "metrics")]
::metrics::counter!(
crate::observability::JWKS_REFRESH_TOTAL,
"issuer_host" => issuer_host.to_owned(),
"result" => result,
)
.increment(1);
#[cfg(not(feature = "metrics"))]
let _ = result;
set_keys(issuer_host, keys);
}
#[inline]
pub(crate) fn set_keys(issuer_host: &str, keys: usize) {
#[cfg(feature = "metrics")]
{
#[allow(clippy::cast_precision_loss)]
::metrics::gauge!(
crate::observability::JWKS_KEYS,
"issuer_host" => issuer_host.to_owned(),
)
.set(keys as f64);
}
#[cfg(not(feature = "metrics"))]
let _ = (issuer_host, keys);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::token::InvalidTokenKind;
#[test]
fn reasons_are_the_stable_labels() {
assert_eq!(reason(&TokenRejection::Missing), "missing");
assert_eq!(
reason(&TokenRejection::InsufficientScope),
"insufficient_scope"
);
assert_eq!(
reason(&TokenRejection::invalid(InvalidTokenKind::Expired, "x")),
"expired"
);
}
#[cfg(feature = "tower")]
#[test]
fn mechanisms_follow_the_credential_then_the_rejection() {
use crate::authenticate::Credential;
let invalid = |kind| TokenRejection::invalid(kind, "x");
for (rejection, want) in [
(TokenRejection::Missing, Mechanism::None),
(TokenRejection::InsufficientScope, Mechanism::OAuth),
(
invalid(InvalidTokenKind::StaticTokenMismatch),
Mechanism::Static,
),
(
invalid(InvalidTokenKind::OAuthTokenRequired),
Mechanism::Static,
),
(invalid(InvalidTokenKind::NoMechanism), Mechanism::None),
(
invalid(InvalidTokenKind::StaticTokenRequired),
Mechanism::OAuth,
),
(invalid(InvalidTokenKind::BadSignature), Mechanism::OAuth),
] {
assert_eq!(Mechanism::of_rejection(&rejection), want, "{rejection:?}");
}
assert_eq!(
Mechanism::of_request(
Some(&Credential::StaticToken),
&TokenRejection::InsufficientScope
),
Mechanism::Static
);
assert_eq!(status(&TokenRejection::InsufficientScope), 403);
assert_eq!(status(&TokenRejection::Missing), 401);
}
}