use tonic::Status;
pub trait ClusterAuth: Send + Sync + 'static {
#[allow(clippy::result_large_err)]
fn authenticate(&self, metadata: &tonic::metadata::MetadataMap) -> Result<(), Status>;
}
pub struct SharedSecretAuth {
expected: String,
}
impl SharedSecretAuth {
pub fn new(token: impl Into<String>) -> Self {
Self {
expected: token.into(),
}
}
}
impl ClusterAuth for SharedSecretAuth {
fn authenticate(&self, metadata: &tonic::metadata::MetadataMap) -> Result<(), Status> {
match metadata.get("authorization") {
Some(value) => {
let value_str = value
.to_str()
.map_err(|_| Status::unauthenticated("invalid authorization header encoding"))?;
if constant_time_eq(value_str.as_bytes(), self.expected.as_bytes()) {
Ok(())
} else {
Err(Status::unauthenticated("invalid authorization token"))
}
}
None => Err(Status::unauthenticated("missing authorization header")),
}
}
}
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut diff = 0u8;
for (x, y) in a.iter().zip(b.iter()) {
diff |= x ^ y;
}
diff == 0
}
impl std::fmt::Debug for SharedSecretAuth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SharedSecretAuth")
.field("token", &"[REDACTED]")
.finish()
}
}