From abc653eb21a46321a87d1fff7a9a3d44d721f512 Mon Sep 17 00:00:00 2001 From: naskya <m@naskya.net> Date: Thu, 6 Jun 2024 08:29:53 +0900 Subject: [PATCH] chore: unsubscribe invalid push subscriptions --- .../src/service/push_notification.rs | 82 +++++++++++++------ 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/packages/backend-rs/src/service/push_notification.rs b/packages/backend-rs/src/service/push_notification.rs index b01f5babd8..3c3745f7fd 100644 --- a/packages/backend-rs/src/service/push_notification.rs +++ b/packages/backend-rs/src/service/push_notification.rs @@ -21,6 +21,8 @@ pub enum Error { Serialize(#[from] serde_json::Error), #[error("Invalid content: {0}")] InvalidContent(String), + #[error("Invalid subscription: {0}")] + InvalidSubscription(String), #[error("Invalid notification ID: {0}")] InvalidId(#[from] InvalidIdError), #[error("HTTP client aquisition error: {0}")] @@ -102,6 +104,40 @@ fn compact_content( Ok(serde_json::from_value(Json::Object(object.clone()))?) } +/// Returns a tuple containing the token and client name +async fn get_mastodon_subscription_info( + db: &DbConn, + subscription_id: &str, + token_id: &str, +) -> Result<(String, String), Error> { + let token = access_token::Entity::find() + .filter(access_token::Column::Id.eq(token_id)) + .one(db) + .await?; + + if token.is_none() { + unsubscribe(db, subscription_id).await?; + return Err(Error::InvalidSubscription( + "access token not found".to_string(), + )); + } + let token = token.unwrap(); + + if token.app_id.is_none() { + unsubscribe(db, subscription_id).await?; + return Err(Error::InvalidSubscription("no app ID".to_string())); + } + let app_id = token.app_id.unwrap(); + + let client = app::Entity::find() + .filter(app::Column::Id.eq(app_id)) + .one(db) + .await? + .ok_or(Error::InvalidSubscription("app not found".to_string()))?; + + Ok((token.token, client.name)) +} + async fn encode_mastodon_payload( mut content: serde_json::Value, db: &DbConn, @@ -111,30 +147,19 @@ async fn encode_mastodon_payload( .as_object_mut() .ok_or(Error::InvalidContent("not a JSON object".to_string()))?; - let token_id = subscription - .app_access_token_id - .as_ref() - .ok_or(Error::InvalidContent("no access token".to_string()))?; - let token = access_token::Entity::find() - .filter(access_token::Column::Id.eq(token_id)) - .one(db) - .await? - .ok_or(Error::InvalidContent("access token not found".to_string()))?; + if subscription.app_access_token_id.is_none() { + unsubscribe(db, &subscription.id).await?; + return Err(Error::InvalidSubscription("no access token".to_string())); + } - let app_id = token - .app_id - .ok_or(Error::InvalidContent("no app ID".to_string()))?; + let (token, client_name) = get_mastodon_subscription_info( + db, + &subscription.id, + subscription.app_access_token_id.as_ref().unwrap(), + ) + .await?; - let client = app::Entity::find() - .filter(app::Column::Id.eq(app_id)) - .one(db) - .await? - .ok_or(Error::InvalidContent("app not found".to_string()))?; - - object.insert( - "access_token".to_string(), - serde_json::to_value(token.token)?, - ); + object.insert("access_token".to_string(), serde_json::to_value(token)?); // Some apps expect notification_id to be an integer, // but doesn’t break when the ID doesn’t match the rest of API. @@ -146,7 +171,7 @@ async fn encode_mastodon_payload( "Metatext", "Feditext", ] - .contains(&client.name.as_str()) + .contains(&client_name.as_str()) { let timestamp = object .get("notification_id") @@ -173,6 +198,13 @@ async fn encode_mastodon_payload( Ok(format!("{}{:pad_length$}", res, "")) } +async fn unsubscribe(db: &DbConn, subscription_id: &str) -> Result<(), DbErr> { + sw_subscription::Entity::delete_by_id(subscription_id) + .exec(db) + .await?; + Ok(()) +} + async fn handle_web_push_failure( db: &DbConn, err: WebPushError, @@ -191,9 +223,7 @@ async fn handle_web_push_failure( | WebPushError::MissingCryptoKeys | WebPushError::InvalidCryptoKeys | WebPushError::InvalidResponse => { - sw_subscription::Entity::delete_by_id(subscription_id) - .exec(db) - .await?; + unsubscribe(db, subscription_id).await?; tracing::info!("{}; {} was unsubscribed", error_message, subscription_id); tracing::debug!("reason: {:#?}", err); }