mini_apm/models/
api_key.rs1use crate::DbPool;
2use chrono::Utc;
3use rand::Rng;
4use sha2::{Digest, Sha256};
5
6const PREFIX: &str = "mini_apm_k_";
7
8#[derive(Debug, Clone)]
9pub struct ApiKey {
10 pub id: i64,
11 pub name: String,
12 pub created_at: String,
13 pub last_used_at: Option<String>,
14}
15
16pub fn create(pool: &DbPool, name: &str) -> anyhow::Result<String> {
17 let conn = pool.get()?;
18
19 let random_bytes: [u8; 24] = rand::thread_rng().r#gen();
21 let raw_key = format!("{}{}", PREFIX, hex::encode(random_bytes));
22 let key_hash = hash_key(&raw_key);
23
24 conn.execute(
25 "INSERT INTO api_keys (name, key_hash, created_at) VALUES (?1, ?2, ?3)",
26 (&name, &key_hash, Utc::now().to_rfc3339()),
27 )?;
28
29 Ok(raw_key)
30}
31
32pub fn verify(pool: &DbPool, raw_key: &str) -> anyhow::Result<bool> {
33 if raw_key.is_empty() {
34 return Ok(false);
35 }
36
37 let conn = pool.get()?;
38 let key_hash = hash_key(raw_key);
39
40 let exists: bool = conn
41 .query_row(
42 "SELECT EXISTS(SELECT 1 FROM api_keys WHERE key_hash = ?1)",
43 [&key_hash],
44 |row| row.get(0),
45 )
46 .unwrap_or(false);
47
48 if exists {
49 let _ = conn.execute(
51 "UPDATE api_keys SET last_used_at = ?1 WHERE key_hash = ?2",
52 (Utc::now().to_rfc3339(), &key_hash),
53 );
54 }
55
56 Ok(exists)
57}
58
59pub fn list(pool: &DbPool) -> anyhow::Result<Vec<ApiKey>> {
60 let conn = pool.get()?;
61 let mut stmt = conn
62 .prepare("SELECT id, name, created_at, last_used_at FROM api_keys ORDER BY created_at")?;
63
64 let keys = stmt
65 .query_map([], |row| {
66 Ok(ApiKey {
67 id: row.get(0)?,
68 name: row.get(1)?,
69 created_at: row.get(2)?,
70 last_used_at: row.get(3)?,
71 })
72 })?
73 .collect::<Result<Vec<_>, _>>()?;
74
75 Ok(keys)
76}
77
78fn hash_key(raw_key: &str) -> String {
79 let mut hasher = Sha256::new();
80 hasher.update(raw_key.as_bytes());
81 hex::encode(hasher.finalize())
82}