1use axum::extract::Request;
2use axum::http::StatusCode;
3use axum::middleware::Next;
4use axum::response::Response;
5use axum::Extension;
6use std::sync::Arc;
7
8const TOKEN_PREFIX: &str = "yule_";
9
10pub struct TokenAuthority {
11 master: [u8; 32],
12 hashes: Vec<[u8; 32]>,
13}
14
15impl TokenAuthority {
16 pub fn new() -> Self {
17 let mut seed = [0u8; 32];
18 getrandom::fill(&mut seed).expect("os entropy failed");
19 Self { master: seed, hashes: Vec::new() }
20 }
21
22 pub fn generate_token(&mut self) -> String {
23 let input = [
24 &self.master[..],
25 &(self.hashes.len() as u64).to_le_bytes(),
26 &std::time::SystemTime::now()
27 .duration_since(std::time::UNIX_EPOCH)
28 .unwrap()
29 .as_nanos()
30 .to_le_bytes(),
31 ].concat();
32
33 let derived = blake3::hash(&input);
34 let hex: String = derived.as_bytes()[..24]
35 .iter()
36 .map(|b| format!("{b:02x}"))
37 .collect();
38 let token = format!("{TOKEN_PREFIX}{hex}");
39
40 self.hashes.push(*blake3::hash(token.as_bytes()).as_bytes());
41 token
42 }
43
44 pub fn from_existing(token: &str) -> Self {
45 let mut auth = Self {
46 master: [0u8; 32],
47 hashes: Vec::new(),
48 };
49 auth.hashes.push(*blake3::hash(token.as_bytes()).as_bytes());
50 auth
51 }
52
53 pub fn verify(&self, provided: &str) -> bool {
54 let hash = *blake3::hash(provided.as_bytes()).as_bytes();
55 self.hashes.iter().any(|h| h == &hash)
56 }
57}
58
59pub async fn require_auth(
60 Extension(auth): Extension<Arc<TokenAuthority>>,
61 req: Request,
62 next: Next,
63) -> std::result::Result<Response, StatusCode> {
64 let token = req.headers()
65 .get("authorization")
66 .and_then(|v| v.to_str().ok())
67 .and_then(|v| v.strip_prefix("Bearer "));
68
69 match token {
70 Some(t) if auth.verify(t) => Ok(next.run(req).await),
71 _ => Err(StatusCode::UNAUTHORIZED),
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn generate_and_verify() {
81 let mut auth = TokenAuthority::new();
82 let token = auth.generate_token();
83 assert!(token.starts_with("yule_"));
84 assert_eq!(token.len(), 5 + 48); assert!(auth.verify(&token));
86 }
87
88 #[test]
89 fn reject_garbage() {
90 let mut auth = TokenAuthority::new();
91 let _ = auth.generate_token();
92 assert!(!auth.verify("not-a-real-token"));
93 assert!(!auth.verify("yule_000000000000000000000000000000000000000000000000"));
94 assert!(!auth.verify(""));
95 }
96
97 #[test]
98 fn multiple_tokens_all_valid() {
99 let mut auth = TokenAuthority::new();
100 let t1 = auth.generate_token();
101 let t2 = auth.generate_token();
102 let t3 = auth.generate_token();
103 assert!(auth.verify(&t1));
104 assert!(auth.verify(&t2));
105 assert!(auth.verify(&t3));
106 }
107
108 #[test]
109 fn tokens_are_unique() {
110 let mut auth = TokenAuthority::new();
111 let t1 = auth.generate_token();
112 let t2 = auth.generate_token();
113 assert_ne!(t1, t2);
114 }
115
116 #[test]
117 fn from_existing_verifies() {
118 let auth = TokenAuthority::from_existing("my-secret-token");
119 assert!(auth.verify("my-secret-token"));
120 assert!(!auth.verify("wrong-token"));
121 }
122
123 #[test]
124 fn stores_hashes_not_plaintext() {
125 let mut auth = TokenAuthority::new();
126 let token = auth.generate_token();
127 for h in &auth.hashes {
129 assert_ne!(h, token.as_bytes().get(..32).unwrap_or(&[]));
130 }
131 }
132}