diff options
Diffstat (limited to 'src/vapid/repo.rs')
| -rw-r--r-- | src/vapid/repo.rs | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/src/vapid/repo.rs b/src/vapid/repo.rs new file mode 100644 index 0000000..4ac5286 --- /dev/null +++ b/src/vapid/repo.rs @@ -0,0 +1,139 @@ +use p256::{NistP256, ecdsa::SigningKey, elliptic_curve::FieldBytes}; +use sqlx::{Sqlite, SqliteConnection, Transaction}; + +use super::{ + History, + event::{Changed, Event}, +}; +use crate::{ + clock::DateTime, + db::NotFound, + event::{Instant, Sequence}, +}; + +pub trait Provider { + fn vapid(&mut self) -> Vapid<'_>; +} + +impl Provider for Transaction<'_, Sqlite> { + fn vapid(&mut self) -> Vapid<'_> { + Vapid(self) + } +} + +pub struct Vapid<'a>(&'a mut SqliteConnection); + +impl Vapid<'_> { + pub async fn record_events( + &mut self, + events: impl IntoIterator<Item = Event>, + ) -> Result<(), sqlx::Error> { + for event in events { + self.record_event(&event).await?; + } + Ok(()) + } + + pub async fn record_event(&mut self, event: &Event) -> Result<(), sqlx::Error> { + match event { + Event::Changed(changed) => self.record_changed(changed).await, + } + } + + async fn record_changed(&mut self, changed: &Changed) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + insert into vapid_key (changed_at, changed_sequence) + values ($1, $2) + "#, + changed.instant.at, + changed.instant.sequence, + ) + .execute(&mut *self.0) + .await?; + + Ok(()) + } + + pub async fn clear(&mut self) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + delete from vapid_key + "# + ) + .execute(&mut *self.0) + .await?; + + sqlx::query!( + r#" + delete from vapid_signing_key + "# + ) + .execute(&mut *self.0) + .await?; + + Ok(()) + } + + pub async fn store_signing_key(&mut self, key: &SigningKey) -> Result<(), Error> { + let key = key.to_bytes(); + let key = key.as_slice(); + sqlx::query!( + r#" + insert into vapid_signing_key (key) + values ($1) + "#, + key, + ) + .execute(&mut *self.0) + .await?; + + Ok(()) + } + + pub async fn current(&mut self) -> Result<History, Error> { + let key = sqlx::query!( + r#" + select + key.changed_at as "changed_at: DateTime", + key.changed_sequence as "changed_sequence: Sequence", + signing.key as "key: Vec<u8>" + from vapid_key as key + join vapid_signing_key as signing + "# + ) + .map(|row| { + let key = FieldBytes::<NistP256>::from_slice(&row.key); + let key = SigningKey::from_bytes(key)?; + let key = key.verifying_key().to_owned(); + + let changed = Instant::new(row.changed_at, row.changed_sequence); + + Ok::<_, Error>(History { key, changed }) + }) + .fetch_one(&mut *self.0) + .await??; + + Ok(key) + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub enum Error { + Ecdsa(#[from] p256::ecdsa::Error), + Database(#[from] sqlx::Error), +} + +impl<T> NotFound for Result<T, Error> { + type Ok = T; + type Error = Error; + + fn optional(self) -> Result<Option<T>, Error> { + match self { + Ok(value) => Ok(Some(value)), + Err(Error::Database(sqlx::Error::RowNotFound)) => Ok(None), + Err(other) => Err(other), + } + } +} |
