sirr_server/store/
keys.rs1use constant_time_eq::constant_time_eq_32;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub struct KeyRecord {
8 pub id: String,
10 pub name: String,
12 pub hash: [u8; 32],
14 pub created_at: i64,
16 pub valid_after: Option<i64>,
18 pub valid_before: Option<i64>,
20 pub webhook_url: Option<String>,
22}
23
24impl KeyRecord {
25 pub fn is_active(&self, now: i64) -> bool {
27 if let Some(after) = self.valid_after {
28 if now < after {
29 return false;
30 }
31 }
32 if let Some(before) = self.valid_before {
33 if now >= before {
34 return false;
35 }
36 }
37 true
38 }
39
40 pub fn verify_token(&self, token: &[u8]) -> bool {
42 let computed = blake3::hash(token);
43 constant_time_eq_32(computed.as_bytes(), &self.hash)
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 fn sample_hash() -> [u8; 32] {
52 *blake3::hash(b"my-secret-token").as_bytes()
53 }
54
55 fn sample() -> KeyRecord {
56 KeyRecord {
57 id: "01HZ_SAMPLE".to_string(),
58 name: "alice".to_string(),
59 hash: sample_hash(),
60 created_at: 1_712_846_400,
61 valid_after: None,
62 valid_before: None,
63 webhook_url: None,
64 }
65 }
66
67 #[test]
68 fn is_active_no_window() {
69 let k = sample();
70 assert!(k.is_active(0));
71 assert!(k.is_active(9_999_999_999));
72 }
73
74 #[test]
75 fn is_active_before_window_start() {
76 let mut k = sample();
77 k.valid_after = Some(1_000_000);
78 assert!(!k.is_active(999_999));
79 assert!(k.is_active(1_000_000));
80 assert!(k.is_active(2_000_000));
81 }
82
83 #[test]
84 fn is_active_after_window_end() {
85 let mut k = sample();
86 k.valid_before = Some(1_000_000);
87 assert!(k.is_active(999_999));
88 assert!(!k.is_active(1_000_000));
89 assert!(!k.is_active(2_000_000));
90 }
91
92 #[test]
93 fn is_active_within_window() {
94 let mut k = sample();
95 k.valid_after = Some(1_000);
96 k.valid_before = Some(2_000);
97 assert!(!k.is_active(999));
98 assert!(k.is_active(1_000));
99 assert!(k.is_active(1_500));
100 assert!(!k.is_active(2_000));
101 }
102
103 #[test]
104 fn verify_token_correct() {
105 let k = sample();
106 assert!(k.verify_token(b"my-secret-token"));
107 }
108
109 #[test]
110 fn verify_token_wrong() {
111 let k = sample();
112 assert!(!k.verify_token(b"wrong-token"));
113 }
114
115 #[test]
116 fn bincode_round_trip() {
117 let record = KeyRecord {
118 id: "01JTEST".to_string(),
119 name: "bob".to_string(),
120 hash: [42u8; 32],
121 created_at: 1_712_846_400,
122 valid_after: Some(1_000_000),
123 valid_before: None,
124 webhook_url: Some("https://example.com/hook".to_string()),
125 };
126
127 let encoded = bincode::serde::encode_to_vec(&record, bincode::config::standard()).unwrap();
128 let (decoded, _): (KeyRecord, _) =
129 bincode::serde::decode_from_slice(&encoded, bincode::config::standard()).unwrap();
130
131 assert_eq!(record, decoded);
132 }
133}