1use std::sync::Arc;
9
10use axum::extract::FromRef;
11
12use crate::ctx::AuthCtx;
13
14#[derive(Clone, Default)]
18pub struct AdminApiKeys(pub Arc<Vec<String>>);
19
20impl AdminApiKeys {
21 pub fn empty() -> Self {
23 Self(Arc::new(Vec::new()))
24 }
25
26 pub fn from_keys<I, S>(iter: I) -> Self
31 where
32 I: IntoIterator<Item = S>,
33 S: Into<String>,
34 {
35 Self(Arc::new(iter.into_iter().map(Into::into).collect()))
36 }
37
38 pub fn check(&self, presented: &str) -> bool {
41 let presented = presented.as_bytes();
42 for key in self.0.iter() {
43 let key = key.as_bytes();
44 if key.len() != presented.len() {
45 continue;
46 }
47 let mut diff = 0u8;
48 for (a, b) in key.iter().zip(presented.iter()) {
49 diff |= a ^ b;
50 }
51 if diff == 0 {
52 return true;
53 }
54 }
55 false
56 }
57
58 pub fn enabled(&self) -> bool {
60 !self.0.is_empty()
61 }
62}
63
64#[derive(Clone)]
67pub struct AuthCtxWithAdmin {
68 pub auth: AuthCtx,
69 pub admin: AdminApiKeys,
70}
71
72impl AuthCtxWithAdmin {
73 pub fn new(auth: AuthCtx) -> Self {
74 Self {
75 auth,
76 admin: AdminApiKeys::empty(),
77 }
78 }
79
80 pub fn with_admin_keys(mut self, admin: AdminApiKeys) -> Self {
81 self.admin = admin;
82 self
83 }
84}
85
86impl FromRef<AuthCtxWithAdmin> for AuthCtx {
87 fn from_ref(s: &AuthCtxWithAdmin) -> Self {
88 s.auth.clone()
89 }
90}
91
92impl FromRef<AuthCtxWithAdmin> for AdminApiKeys {
93 fn from_ref(s: &AuthCtxWithAdmin) -> Self {
94 s.admin.clone()
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn admin_keys_constant_time_check() {
104 let keys = AdminApiKeys::from_keys(["abc", "xyz"]);
105 assert!(keys.check("abc"));
106 assert!(keys.check("xyz"));
107 assert!(!keys.check("abd"));
108 assert!(!keys.check(""));
109 assert!(!keys.check("abcd"));
110 }
111
112 #[test]
113 fn admin_keys_empty_disables() {
114 let keys = AdminApiKeys::empty();
115 assert!(!keys.enabled());
116 assert!(!keys.check("anything"));
117 }
118
119 #[test]
120 fn admin_keys_enabled_when_populated() {
121 let keys = AdminApiKeys::from_keys(["k"]);
122 assert!(keys.enabled());
123 }
124}