diff options
| author | ojacobson <ojacobson@noreply.codeberg.org> | 2025-11-07 23:17:15 +0100 |
|---|---|---|
| committer | ojacobson <ojacobson@noreply.codeberg.org> | 2025-11-07 23:17:15 +0100 |
| commit | 9e6f19f0f188eaa7f8b6be21c8405786cfb0dddd (patch) | |
| tree | b2999341645dec61e8143d7bb1b8a9d0056e0db1 /src/push/repo.rs | |
| parent | 3c588861ef5814de329743147398dbae22c1aeeb (diff) | |
| parent | 78d901328261d2306cf59c8e83fc217a63aa4a64 (diff) | |
Set up infrastructure for push message subscriptions.
A subscription allows an application server (here, the Pilcrow server) to send web push messages to a user agent.
On the server, Pilcrow records subscriptions verbatim, in the clear. Each subscription has an associated key, which will be used to encrypt messages for the corresponding client, but we store them in the clear, for the same broad reason that we store the VAPID key in the clear. They allow anyone who obtains them to impersonate the server and send push messages to clients, but they're rotated regularly - clients must rotate them whenever the server's VAPID key changes.
On the client, we monitor VAPID key change events to drive automatic subscription management, once the user sets up an initial subscription manually (which we must do as it can involve a user-interaction-only prompt for permission to send notifications). This isn't the final UI, but rather a bare-minimum version to let us move on with testing push notifications.
Merges push-subscribe into push-notify.
Diffstat (limited to 'src/push/repo.rs')
| -rw-r--r-- | src/push/repo.rs | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/src/push/repo.rs b/src/push/repo.rs new file mode 100644 index 0000000..6c18c6e --- /dev/null +++ b/src/push/repo.rs @@ -0,0 +1,114 @@ +use sqlx::{Sqlite, SqliteConnection, Transaction}; +use web_push::SubscriptionInfo; + +use crate::{login::Login, token::Token}; + +pub trait Provider { + fn push(&mut self) -> Push<'_>; +} + +impl Provider for Transaction<'_, Sqlite> { + fn push(&mut self) -> Push<'_> { + Push(self) + } +} + +pub struct Push<'t>(&'t mut SqliteConnection); + +impl Push<'_> { + pub async fn create( + &mut self, + token: &Token, + subscription: &SubscriptionInfo, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + insert into push_subscription (token, endpoint, p256dh, auth) + values ($1, $2, $3, $4) + "#, + token.id, + subscription.endpoint, + subscription.keys.p256dh, + subscription.keys.auth, + ) + .execute(&mut *self.0) + .await?; + + Ok(()) + } + + pub async fn by_endpoint( + &mut self, + subscriber: &Login, + endpoint: &str, + ) -> Result<SubscriptionInfo, sqlx::Error> { + let row = sqlx::query!( + r#" + select + subscription.endpoint, + subscription.p256dh, + subscription.auth + from push_subscription as subscription + join token on subscription.token = token.id + join login as subscriber on token.login = subscriber.id + where subscriber.id = $1 + and subscription.endpoint = $2 + "#, + subscriber.id, + endpoint, + ) + .fetch_one(&mut *self.0) + .await?; + + let info = SubscriptionInfo::new(row.endpoint, row.p256dh, row.auth); + + Ok(info) + } + + pub async fn unsubscribe_token(&mut self, token: &Token) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + delete from push_subscription + where token = $1 + "#, + token.id, + ) + .execute(&mut *self.0) + .await?; + + Ok(()) + } + + pub async fn unsubscribe_login(&mut self, login: &Login) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + with tokens as ( + select id from token + where login = $1 + ) + delete from push_subscription + where token in tokens + "#, + login.id, + ) + .execute(&mut *self.0) + .await?; + + Ok(()) + } + + // Unsubscribe logic for token expiry lives in the `tokens` repository, for maintenance reasons. + + pub async fn clear(&mut self) -> Result<(), sqlx::Error> { + // We assume that _all_ stored subscriptions are for a VAPID key we're about to delete. + sqlx::query!( + r#" + delete from push_subscription + "#, + ) + .execute(&mut *self.0) + .await?; + + Ok(()) + } +} |
