use std::sync::Arc;
use axum::extract::FromRef;
use crate::ctx::AuthCtx;
#[derive(Clone, Default)]
pub struct AdminApiKeys(pub Arc<Vec<String>>);
impl AdminApiKeys {
pub fn empty() -> Self {
Self(Arc::new(Vec::new()))
}
pub fn from_keys<I, S>(iter: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self(Arc::new(iter.into_iter().map(Into::into).collect()))
}
pub fn check(&self, presented: &str) -> bool {
let presented = presented.as_bytes();
for key in self.0.iter() {
let key = key.as_bytes();
if key.len() != presented.len() {
continue;
}
let mut diff = 0u8;
for (a, b) in key.iter().zip(presented.iter()) {
diff |= a ^ b;
}
if diff == 0 {
return true;
}
}
false
}
pub fn enabled(&self) -> bool {
!self.0.is_empty()
}
}
#[derive(Clone)]
pub struct AuthCtxWithAdmin {
pub auth: AuthCtx,
pub admin: AdminApiKeys,
}
impl AuthCtxWithAdmin {
pub fn new(auth: AuthCtx) -> Self {
Self {
auth,
admin: AdminApiKeys::empty(),
}
}
pub fn with_admin_keys(mut self, admin: AdminApiKeys) -> Self {
self.admin = admin;
self
}
}
impl FromRef<AuthCtxWithAdmin> for AuthCtx {
fn from_ref(s: &AuthCtxWithAdmin) -> Self {
s.auth.clone()
}
}
impl FromRef<AuthCtxWithAdmin> for AdminApiKeys {
fn from_ref(s: &AuthCtxWithAdmin) -> Self {
s.admin.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn admin_keys_constant_time_check() {
let keys = AdminApiKeys::from_keys(["abc", "xyz"]);
assert!(keys.check("abc"));
assert!(keys.check("xyz"));
assert!(!keys.check("abd"));
assert!(!keys.check(""));
assert!(!keys.check("abcd"));
}
#[test]
fn admin_keys_empty_disables() {
let keys = AdminApiKeys::empty();
assert!(!keys.enabled());
assert!(!keys.check("anything"));
}
#[test]
fn admin_keys_enabled_when_populated() {
let keys = AdminApiKeys::from_keys(["k"]);
assert!(keys.enabled());
}
}