Skip to main content

acme_proxy/cli/
nonce.rs

1use std::io::BufRead;
2use std::sync::Arc;
3use std::time::Duration;
4
5use clap::Subcommand;
6
7use crate::admin;
8use crate::cli::CliError;
9use crate::config::Config;
10use crate::sqlite::db::Database;
11use crate::sqlite::nonce::Nonce;
12
13#[derive(Subcommand)]
14pub enum NonceCommand {
15    /// Delete nonces older than the TTL.
16    Cleanup {
17        #[arg(long = "ttl-seconds")]
18        ttl_seconds: Option<u64>,
19    },
20    /// How many nonces the table holds, and the window they are fresh for.
21    Count {
22        #[arg(long)]
23        json: bool,
24    },
25}
26
27pub async fn run_nonce_command(
28    command: NonceCommand,
29    yes: bool,
30    reader: &mut impl BufRead,
31    config: &Config,
32    database: Arc<Database>,
33) -> Result<(), CliError> {
34    match command {
35        NonceCommand::Cleanup { ttl_seconds } => {
36            let ttl = Duration::from_secs(ttl_seconds.unwrap_or(config.nonce.ttl_seconds));
37            match admin::confirm_cleanup_nonces(ttl, yes, reader, database).await? {
38                None => println!("Cancelled."),
39                Some(removed) => println!("Removed {removed} nonce(s)."),
40            }
41        }
42        NonceCommand::Count { json } => {
43            // No `Palette`: a count is data, and the only thing here that could
44            // be painted -- "the reaper is not running" -- is a judgement this
45            // command deliberately leaves to the operator reading the two
46            // numbers together.
47            let count = Nonce::count(&database).await?;
48            let ttl = config.nonce.ttl_seconds;
49            if json {
50                println!("{}", admin::render_nonce_stats_json(count, ttl));
51            } else {
52                println!("{count} nonce(s), ttl {ttl}s.");
53            }
54        }
55    }
56    Ok(())
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    /// Both shapes, and the pair the count is only meaningful as: the number on
64    /// its own says nothing without the window it is a count over.
65    #[tokio::test]
66    async fn count_reports_the_table_and_the_configured_ttl() {
67        let database = Arc::new(Database::connect_in_memory().await.unwrap());
68        let mut config = Config::default();
69        config.nonce.ttl_seconds = 42;
70
71        for _ in 0..3 {
72            Nonce::new().save(&database).await.unwrap();
73        }
74        assert_eq!(Nonce::count(&database).await.unwrap(), 3);
75
76        assert_eq!(
77            admin::render_nonce_stats_json(3, config.nonce.ttl_seconds),
78            serde_json::json!({ "count": 3, "ttlSeconds": 42 }),
79        );
80
81        for json in [false, true] {
82            let mut reader: &[u8] = &[];
83            run_nonce_command(
84                NonceCommand::Count { json },
85                true,
86                &mut reader,
87                &config,
88                database.clone(),
89            )
90            .await
91            .unwrap();
92        }
93    }
94
95    /// Omitting `--ttl-seconds` falls back to `nonce.ttl_seconds`, and a
96    /// declined confirmation sweeps nothing without being a failure.
97    #[tokio::test]
98    async fn cleanup_honours_the_configured_ttl_and_the_prompt() {
99        let database = Arc::new(
100            crate::sqlite::db::Database::connect_in_memory()
101                .await
102                .unwrap(),
103        );
104        let mut config = Config::default();
105        config.nonce.ttl_seconds = 1;
106
107        let mut declined: &[u8] = b"n\n";
108        run_nonce_command(
109            NonceCommand::Cleanup { ttl_seconds: None },
110            false,
111            &mut declined,
112            &config,
113            database.clone(),
114        )
115        .await
116        .unwrap();
117
118        let mut reader: &[u8] = &[];
119        run_nonce_command(
120            NonceCommand::Cleanup {
121                ttl_seconds: Some(60),
122            },
123            true,
124            &mut reader,
125            &config,
126            database,
127        )
128        .await
129        .unwrap();
130    }
131}