1use chrono::{DateTime, Utc};
2use rand::Rng;
3use serde::{Deserialize, Serialize};
4use sha2::{Digest, Sha256};
5
6use crate::storage::{StorageBackend, StorageEntry, StorageError};
7
8const TOKEN_PREFIX: &str = "sys/token/";
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct TokenEntry {
12 pub id_hash: String,
13 pub policies: Vec<String>,
14 pub created_at: DateTime<Utc>,
15 pub expires_at: Option<DateTime<Utc>>,
16}
17
18pub fn generate_token(policies: Vec<String>, ttl_seconds: Option<i64>) -> (String, TokenEntry) {
21 let mut raw = [0u8; 24];
22 rand::rng().fill_bytes(&mut raw);
23 let token = format!("s.{}", hex::encode(raw));
24 let now = Utc::now();
25 let entry = TokenEntry {
26 id_hash: hash_token(&token),
27 policies,
28 created_at: now,
29 expires_at: ttl_seconds.map(|s| now + chrono::Duration::seconds(s)),
30 };
31 (token, entry)
32}
33
34pub fn hash_token(token: &str) -> String {
35 hex::encode(Sha256::digest(token.as_bytes()))
36}
37
38pub async fn store_token(
39 storage: &dyn StorageBackend,
40 token: &str,
41 entry: &TokenEntry,
42) -> Result<(), StorageError> {
43 let path = format!("{TOKEN_PREFIX}{}", hash_token(token));
44 let value = serde_json::to_vec(entry).map_err(|e| StorageError::Backend(e.to_string()))?;
45 storage
46 .put(
47 &path,
48 StorageEntry {
49 value,
50 expires_at: entry.expires_at,
51 },
52 )
53 .await
54}
55
56pub async fn lookup_token(
58 storage: &dyn StorageBackend,
59 token: &str,
60) -> Result<Option<TokenEntry>, StorageError> {
61 let path = format!("{TOKEN_PREFIX}{}", hash_token(token));
62 let Some(entry) = storage.get(&path).await? else {
63 return Ok(None);
64 };
65 if let Some(expires_at) = entry.expires_at
66 && expires_at < Utc::now()
67 {
68 return Ok(None);
69 }
70 let token_entry: TokenEntry =
71 serde_json::from_slice(&entry.value).map_err(|e| StorageError::Backend(e.to_string()))?;
72 Ok(Some(token_entry))
73}
74
75pub async fn revoke_token(storage: &dyn StorageBackend, token: &str) -> Result<(), StorageError> {
76 let path = format!("{TOKEN_PREFIX}{}", hash_token(token));
77 storage.delete(&path).await
78}
79
80pub async fn renew_token(
81 storage: &dyn StorageBackend,
82 token: &str,
83 ttl_seconds: i64,
84) -> Result<Option<TokenEntry>, StorageError> {
85 let Some(mut entry) = lookup_token(storage, token).await? else {
86 return Ok(None);
87 };
88 entry.expires_at = Some(Utc::now() + chrono::Duration::seconds(ttl_seconds));
89 store_token(storage, token, &entry).await?;
90 Ok(Some(entry))
91}