Skip to content

Commit

Permalink
feat: APNS Router (#201)
Browse files Browse the repository at this point in the history
* Start working on the APNS router

* Use a fork of a2 temporarily to support our use case

Changes made in the fork:
1. Support creating a client with the raw cert and key data (no
   password).
2. Impl Deserialize for the APS struct (and sub-structs)

Also improved error handling during APNS router creation, and started on
the router impl.

* Implement the APNS router happy path

* Handle APNS errors

* Switch to the "autoendpoint" branch of the a2 fork

This branch contains all of the changes made in the fork.

* Implement Router::register for ApnsRouter

* Remove old TooMuchData error

* Abstract the a2 client behind a trait and add a bunch of APNS tests

* Convert float values in APS data from DynamoDb into integers

Also, if the user has invalid APS data in the DB, an error is returned
during routing.

* Remove redundant APNS metric

* Give APNS an expiration timestamp when routing notifications

* Check the final APNS payload against the size limit

* Drop the user if APNS says they are unregistered

* Add some details about the a2 fork

Closes #164
  • Loading branch information
AzureMarker authored Jul 28, 2020
1 parent 8c4f23b commit ce51957
Show file tree
Hide file tree
Showing 14 changed files with 922 additions and 94 deletions.
70 changes: 70 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion autoendpoint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ authors = ["Mark Drobnak <[email protected]>", "jrconlin <[email protected]
edition = "2018"

[dependencies]
# Using a fork temporarily until these three PRs are merged:
# - https://github.com/pimeys/a2/pull/49
# - https://github.com/pimeys/a2/pull/48
# - https://github.com/pimeys/a2/pull/47
# The `autoendpoint` branch merges these three PRs together.
# The version of a2 at the time of the fork is v0.5.3.
a2 = { git = "https://github.com/Mcat12/a2.git", branch = "autoendpoint" }
actix-web = "2.0"
actix-rt = "1.0"
actix-cors = "0.2.0"
Expand Down Expand Up @@ -38,6 +45,7 @@ slog-mozlog-json = "0.1"
slog-scope = "4.3"
slog-stdlog = "4.0"
slog-term = "2.5"
tokio = { version = "0.2", features = ["fs"] }
thiserror = "1.0"
url = "2.1"
uuid = { version = "0.8.1", features = ["serde", "v4"] }
Expand All @@ -49,4 +57,4 @@ yup-oauth2 = "4.1.2"
mockall = "0.7.1"
mockito = "0.26.0"
tempfile = "3.1.0"
tokio = { version = "0.2.12", features = ["macros"] }
tokio = { version = "0.2", features = ["macros"] }
3 changes: 1 addition & 2 deletions autoendpoint/src/extractors/router_data_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use futures::future::LocalBoxFuture;
use futures::FutureExt;
use lazy_static::lazy_static;
use regex::Regex;
use std::collections::HashMap;
use uuid::Uuid;

lazy_static! {
Expand All @@ -24,7 +23,7 @@ pub struct RouterDataInput {
#[serde(rename = "channelID")]
pub channel_id: Option<Uuid>,
pub key: Option<String>,
pub aps: Option<HashMap<String, serde_json::Value>>,
pub aps: Option<serde_json::Value>,
}

impl FromRequest for RouterDataInput {
Expand Down
5 changes: 4 additions & 1 deletion autoendpoint/src/extractors/routers.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::routers::apns::router::ApnsRouter;
use crate::routers::fcm::router::FcmRouter;
use crate::routers::webpush::WebPushRouter;
use crate::routers::Router;
Expand Down Expand Up @@ -49,6 +50,7 @@ impl Display for RouterType {
pub struct Routers {
webpush: WebPushRouter,
fcm: Arc<FcmRouter>,
apns: Arc<ApnsRouter>,
}

impl FromRequest for Routers {
Expand All @@ -69,6 +71,7 @@ impl FromRequest for Routers {
endpoint_url: state.settings.endpoint_url.clone(),
},
fcm: state.fcm_router.clone(),
apns: state.apns_router.clone(),
})
}
}
Expand All @@ -79,7 +82,7 @@ impl Routers {
match router_type {
RouterType::WebPush => &self.webpush,
RouterType::FCM => self.fcm.as_ref(),
RouterType::APNS => unimplemented!(),
RouterType::APNS => self.apns.as_ref(),
RouterType::ADM => unimplemented!(),
}
}
Expand Down
82 changes: 82 additions & 0 deletions autoendpoint/src/routers/apns/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use crate::error::ApiErrorKind;
use crate::routers::RouterError;
use actix_web::http::StatusCode;
use std::io;

/// Errors that may occur in the Apple Push Notification Service router
#[derive(thiserror::Error, Debug)]
pub enum ApnsError {
#[error("Failed to decode the channel settings")]
ChannelSettingsDecode(#[from] serde_json::Error),

#[error("IO Error: {0}")]
Io(#[from] io::Error),

#[error("Error while setting up APNS clients: {0}")]
ApnsClient(#[source] a2::Error),

#[error("Error while checking the message size limit: {0}")]
SizeLimit(#[source] a2::Error),

#[error("APNS error, {0}")]
ApnsUpstream(#[source] a2::Error),

#[error("No device token found for user")]
NoDeviceToken,

#[error("No release channel found for user")]
NoReleaseChannel,

#[error("Release channel is invalid")]
InvalidReleaseChannel,

#[error("Invalid APS data")]
InvalidApsData,

#[error("APNS recipient no longer available")]
Unregistered,
}

impl ApnsError {
/// Get the associated HTTP status code
pub fn status(&self) -> StatusCode {
match self {
ApnsError::InvalidReleaseChannel
| ApnsError::InvalidApsData
| ApnsError::SizeLimit(_) => StatusCode::BAD_REQUEST,

ApnsError::NoDeviceToken | ApnsError::NoReleaseChannel | ApnsError::Unregistered => {
StatusCode::GONE
}

ApnsError::ChannelSettingsDecode(_) | ApnsError::Io(_) | ApnsError::ApnsClient(_) => {
StatusCode::INTERNAL_SERVER_ERROR
}

ApnsError::ApnsUpstream(_) => StatusCode::BAD_GATEWAY,
}
}

/// Get the associated error number
pub fn errno(&self) -> Option<usize> {
match self {
ApnsError::NoDeviceToken | ApnsError::NoReleaseChannel | ApnsError::Unregistered => {
Some(106)
}

ApnsError::ChannelSettingsDecode(_)
| ApnsError::Io(_)
| ApnsError::ApnsClient(_)
| ApnsError::ApnsUpstream(_)
| ApnsError::InvalidReleaseChannel
| ApnsError::InvalidApsData
| ApnsError::SizeLimit(_) => None,
}
}
}

impl From<ApnsError> for ApiErrorKind {
fn from(e: ApnsError) -> Self {
ApiErrorKind::Router(RouterError::Apns(e))
}
}
5 changes: 5 additions & 0 deletions autoendpoint/src/routers/apns/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//! A notification router for Apple devices, using Apple Push Notification Service
pub mod error;
pub mod router;
pub mod settings;
Loading

0 comments on commit ce51957

Please sign in to comment.