use chrono::TimeDelta; use sqlx::SqlitePool; use super::{History, repo, repo::Provider as _}; use crate::{ clock::DateTime, db::NotFound as _, event::{Broadcaster, Sequence, repo::Provider}, }; pub struct Vapid<'a> { db: &'a SqlitePool, events: &'a Broadcaster, } impl<'a> Vapid<'a> { pub const fn new(db: &'a SqlitePool, events: &'a Broadcaster) -> Self { Self { db, events } } pub async fn rotate_key(&self) -> Result<(), sqlx::Error> { let mut tx = self.db.begin().await?; // This is called from a separate CLI utility (see `cli.rs`), and we _can't_ deliver events // to active clients from another process, so don't do anything that would require us to // send events, like generating a new key. // // Instead, the server's next `refresh_key` call will generate a key and notify clients // of the change. All we have to do is remove the existing key, so that the server can know // to do so. tx.vapid().clear().await?; tx.commit().await?; Ok(()) } pub async fn refresh_key(&self, ensure_at: &DateTime) -> Result<(), Error> { let mut tx = self.db.begin().await?; let key = tx.vapid().current().await.optional()?; if key.is_none() { let changed_at = tx.sequence().next(ensure_at).await?; let (key, secret) = History::begin(&changed_at); tx.vapid().clear().await?; tx.vapid().store_signing_key(&secret).await?; let events = key.events().filter(Sequence::start_from(changed_at)); tx.vapid().record_events(events.clone()).await?; tx.commit().await?; self.events.broadcast_from(events); } else if let Some(key) = key // Somewhat arbitrarily, rotate keys every 30 days. && key.older_than(ensure_at.to_owned() - TimeDelta::days(30)) { // If you can think of a way to factor out this duplication, be my guest. I tried. // The only approach I could think of mirrors `crate::user::create::Create`, encoding // the process in a state machine made of types, and that's a very complex solution // to a problem that doesn't seem to merit it. -o let changed_at = tx.sequence().next(ensure_at).await?; let (key, secret) = key.rotate(&changed_at); tx.vapid().clear().await?; tx.vapid().store_signing_key(&secret).await?; // Refactoring constraint: this `events` iterator borrows `key`. Anything that moves // `key` has to give it back, but it can't give both `key` back and an event iterator // borrowing from `key` because Rust doesn't support types that borrow from other // parts of themselves. let events = key.events().filter(Sequence::start_from(changed_at)); tx.vapid().record_events(events.clone()).await?; // Refactoring constraint: we _really_ want to commit the transaction before we send // out events, so that anything acting on those events is guaranteed to see the state // of the service at some point at or after the side effects of this. I'd also prefer // to keep the commit in the same method that the transaction is begun in, for clarity. tx.commit().await?; self.events.broadcast_from(events); } // else, the key exists and is not stale. Don't bother allocating a sequence number, and // in fact throw away the whole transaction. Ok(()) } } #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] Database(#[from] sqlx::Error), #[error(transparent)] Ecdsa(#[from] p256::ecdsa::Error), } impl From for Error { fn from(error: repo::Error) -> Self { use repo::Error; match error { Error::Database(error) => error.into(), Error::Ecdsa(error) => error.into(), } } }