1use crate::{error::Result, LICENSE_PREFIX};
4use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
5use chrono::{DateTime, Duration, Utc};
6use hmac::{Hmac, Mac};
7use sha2::Sha256;
8
9type HmacSha256 = Hmac<Sha256>;
10
11pub struct LicenseGenerator {
13 secret_key: String,
14}
15
16impl LicenseGenerator {
17 pub fn new<S: Into<String>>(secret_key: S) -> Self {
28 Self {
29 secret_key: secret_key.into(),
30 }
31 }
32
33 pub fn generate_key(&self, hours: u64) -> Result<String> {
48 self.generate_key_with_timestamp(hours, Utc::now())
49 }
50
51 pub fn generate_key_with_timestamp(
53 &self,
54 hours: u64,
55 issued_at: DateTime<Utc>,
56 ) -> Result<String> {
57 let expires_at = issued_at + Duration::hours(hours as i64);
59
60 let payload = format!("{}:{}", issued_at.timestamp(), expires_at.timestamp());
62
63 let mut mac = HmacSha256::new_from_slice(self.secret_key.as_bytes())
65 .expect("HMAC can take key of any size");
66 mac.update(payload.as_bytes());
67 let signature = mac.finalize().into_bytes();
68
69 let mut data = Vec::new();
71 data.extend_from_slice(payload.as_bytes());
72 data.push(b'.');
73 data.extend_from_slice(&signature);
74
75 let encoded = URL_SAFE_NO_PAD.encode(&data);
77
78 Ok(format!("{}{}", LICENSE_PREFIX, encoded))
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn test_generate_key() {
89 let generator = LicenseGenerator::new("test-secret");
90 let key = generator.generate_key(24).unwrap();
91
92 assert!(key.starts_with(LICENSE_PREFIX));
93 assert!(key.len() > LICENSE_PREFIX.len());
94 }
95
96 #[test]
97 fn test_different_hours() {
98 let generator = LicenseGenerator::new("test-secret");
99 let key1 = generator.generate_key(1).unwrap();
100 let key24 = generator.generate_key(24).unwrap();
101 let key_year = generator.generate_key(24 * 365).unwrap();
102
103 assert!(key1.starts_with(LICENSE_PREFIX));
105 assert!(key24.starts_with(LICENSE_PREFIX));
106 assert!(key_year.starts_with(LICENSE_PREFIX));
107
108 assert_ne!(key1, key24);
110 assert_ne!(key24, key_year);
111 }
112}