use p256::ecdsa::VerifyingKey; use sqlx::SqlitePool; use web_push::SubscriptionInfo; use super::repo::Provider as _; use crate::{token::extract::Identity, vapid, vapid::repo::Provider as _}; pub struct Push { db: SqlitePool, } impl Push { pub const fn new(db: SqlitePool) -> Self { Self { db } } pub async fn subscribe( &self, subscriber: &Identity, subscription: &SubscriptionInfo, vapid: &VerifyingKey, ) -> Result<(), SubscribeError> { let mut tx = self.db.begin().await?; let current = tx.vapid().current().await?; if vapid != ¤t.key { return Err(SubscribeError::StaleVapidKey(current.key)); } match tx.push().create(&subscriber.token, subscription).await { Ok(()) => (), Err(err) => { if let Some(err) = err.as_database_error() && err.is_unique_violation() { let current = tx .push() .by_endpoint(&subscriber.login, &subscription.endpoint) .await?; // If we already have a subscription for this endpoint, with _different_ // parameters, then this is a client error. They shouldn't reuse endpoint URLs, // per the various RFCs. // // However, if we have a subscription for this endpoint with the same parameters // then we accept it and silently do nothing. This may happen if, for example, // the subscribe request is retried due to a network interruption where it's // not clear whether the original request succeeded. if ¤t != subscription { return Err(SubscribeError::Duplicate); } } else { return Err(SubscribeError::Database(err)); } } } tx.commit().await?; Ok(()) } } #[derive(Debug, thiserror::Error)] pub enum SubscribeError { #[error(transparent)] Database(#[from] sqlx::Error), #[error(transparent)] Vapid(#[from] vapid::repo::Error), #[error("subscription created with stale VAPID key")] StaleVapidKey(VerifyingKey), #[error("subscription already exists for endpoint")] // The endpoint URL is not included in the error, as it is a bearer credential in its own right // and we want to limit its proliferation. The only intended recipient of this message is the // client, which already knows the endpoint anyways and doesn't need us to tell them. Duplicate, }