use rusty_gasket::auth::Identity;
#[derive(Debug, Clone)]
pub struct AuthContext {
identity: Option<Identity>,
client_ip: String,
request_id: String,
auth_result: AuthResult,
}
impl AuthContext {
pub(crate) const fn new(
identity: Option<Identity>,
client_ip: String,
request_id: String,
auth_result: AuthResult,
) -> Self {
Self {
identity,
client_ip,
request_id,
auth_result,
}
}
#[must_use]
pub const fn identity(&self) -> Option<&Identity> {
self.identity.as_ref()
}
#[must_use]
pub fn client_ip(&self) -> &str {
&self.client_ip
}
#[must_use]
pub fn request_id(&self) -> &str {
&self.request_id
}
#[must_use]
pub const fn auth_result(&self) -> &AuthResult {
&self.auth_result
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum AuthResult {
Authenticated { method: &'static str },
Anonymous,
Failed(FailedReason),
}
#[derive(Debug, Clone)]
pub struct FailedReason(String);
const MAX_FAILED_REASON_LEN: usize = 512;
impl FailedReason {
#[must_use]
pub fn new(reason: impl Into<String>) -> Self {
let raw = reason.into();
let mut sanitized = String::with_capacity(raw.len().min(MAX_FAILED_REASON_LEN));
for ch in raw.chars() {
if sanitized.len() + ch.len_utf8() > MAX_FAILED_REASON_LEN {
break;
}
if !ch.is_control() {
sanitized.push(ch);
}
}
Self(sanitized)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AuthResult {
#[must_use]
pub const fn category(&self) -> &'static str {
match self {
Self::Authenticated { .. } => "authenticated",
Self::Anonymous => "anonymous",
Self::Failed(_) => "failed",
}
}
#[must_use]
pub fn failure_reason(&self) -> Option<&str> {
match self {
Self::Failed(r) => Some(r.as_str()),
_ => None,
}
}
}
impl std::fmt::Display for AuthResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Authenticated { method } => write!(f, "authenticated:{method}"),
Self::Anonymous => f.write_str("anonymous"),
Self::Failed(_) => f.write_str("failed"),
}
}
}