acme_proxy_store/
http01_token.rs1use tracing::debug;
9
10use crate::db::Database;
11
12pub struct Http01Token;
14
15impl Http01Token {
16 pub async fn publish(
22 token: &str,
23 key_authorization: &str,
24 now: i64,
25 expires_at: i64,
26 database: &Database,
27 ) -> Result<(), sqlx::Error> {
28 crate::sql::query(
29 "INSERT INTO http01_tokens (token, key_authorization, created_at, expires_at) \
30 VALUES (?, ?, ?, ?) \
31 ON CONFLICT (token) DO UPDATE SET \
32 key_authorization = excluded.key_authorization, expires_at = excluded.expires_at;",
33 )
34 .bind(token)
35 .bind(key_authorization)
36 .bind(now)
37 .bind(expires_at)
38 .execute(database)
39 .await?;
40 debug!(event = "db_http_01_token_published", outcome = "success", token = %token, expires_at);
41 Ok(())
42 }
43
44 pub async fn retract(token: &str, database: &Database) -> Result<(), sqlx::Error> {
46 let result = crate::sql::query("DELETE FROM http01_tokens WHERE token = ?;")
47 .bind(token)
48 .execute(database)
49 .await?;
50 debug!(
51 event = "db_http_01_token_retracted",
52 outcome = "success",
53 token = %token,
54 rows_removed = result.rows_affected(),
55 );
56 Ok(())
57 }
58
59 pub async fn lookup(
61 token: &str,
62 now: i64,
63 database: &Database,
64 ) -> Result<Option<String>, sqlx::Error> {
65 crate::sql::query(
66 "SELECT key_authorization FROM http01_tokens WHERE token = ? AND expires_at > ?;",
67 )
68 .bind(token)
69 .bind(now)
70 .fetch_optional(database)
71 .await?
72 .map(|row| row.try_get("key_authorization"))
73 .transpose()
74 }
75
76 pub async fn cleanup(now: i64, database: &Database) -> Result<u64, sqlx::Error> {
78 let result = crate::sql::query("DELETE FROM http01_tokens WHERE expires_at <= ?;")
79 .bind(now)
80 .execute(database)
81 .await?;
82 debug!(
85 event = "db_http_01_token_cleanup_completed",
86 outcome = "success",
87 rows_removed = result.rows_affected(),
88 cutoff = now,
89 );
90 Ok(result.rows_affected())
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[tokio::test]
99 async fn a_published_token_is_looked_up_until_retracted() {
100 let database = Database::connect_for_test().await.unwrap();
101 assert_eq!(
102 Http01Token::lookup("tok", 10, &database).await.unwrap(),
103 None
104 );
105
106 Http01Token::publish("tok", "tok.one", 10, 100, &database)
107 .await
108 .unwrap();
109 assert_eq!(
110 Http01Token::lookup("tok", 10, &database).await.unwrap(),
111 Some("tok.one".to_string())
112 );
113
114 Http01Token::retract("tok", &database).await.unwrap();
115 Http01Token::retract("tok", &database).await.unwrap();
116 assert_eq!(
117 Http01Token::lookup("tok", 10, &database).await.unwrap(),
118 None
119 );
120 }
121
122 #[tokio::test]
124 async fn publishing_again_replaces_the_row() {
125 let database = Database::connect_for_test().await.unwrap();
126 Http01Token::publish("tok", "tok.one", 10, 20, &database)
127 .await
128 .unwrap();
129 Http01Token::publish("tok", "tok.two", 15, 200, &database)
130 .await
131 .unwrap();
132
133 assert_eq!(
134 Http01Token::lookup("tok", 100, &database).await.unwrap(),
135 Some("tok.two".to_string())
136 );
137 }
138
139 #[tokio::test]
141 async fn an_expired_token_is_not_served_and_is_swept() {
142 let database = Database::connect_for_test().await.unwrap();
143 Http01Token::publish("old", "old.ka", 0, 50, &database)
144 .await
145 .unwrap();
146 Http01Token::publish("live", "live.ka", 0, 500, &database)
147 .await
148 .unwrap();
149
150 assert_eq!(
151 Http01Token::lookup("old", 50, &database).await.unwrap(),
152 None
153 );
154 assert_eq!(Http01Token::cleanup(50, &database).await.unwrap(), 1);
155 assert_eq!(
156 Http01Token::lookup("live", 50, &database).await.unwrap(),
157 Some("live.ka".to_string())
158 );
159 }
160}