diff --git a/src/error.rs b/src/error.rs index bb1e1a3d..de55b4a7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -169,12 +169,15 @@ pub enum Error { #[error("tenant id and client's registered tenant didn't match")] MissmatchedTenantId, - #[error("invalid fcm api key")] + #[error("Invalid FCM API key")] BadFcmApiKey, - #[error("invalid apns creds")] + #[error("Invalid APNs creds")] BadApnsCredentials, + #[error("Expired APNs certificate")] + ApnsCertificateExpired, + #[error("client deleted due to invalid device token")] ClientDeleted, @@ -568,11 +571,11 @@ impl IntoResponse for Error { }.into_response(); if response.status().is_client_error() { - warn!("HTTP Client Error: {self:?}"); + warn!("HTTP client error: {self:?}"); } if response.status().is_server_error() { - error!("HTTP Server Error: {self:?}"); + error!("HTTP server error: {self:?}"); } response diff --git a/src/handlers/push_message.rs b/src/handlers/push_message.rs index 126ac84f..86d092b1 100644 --- a/src/handlers/push_message.rs +++ b/src/handlers/push_message.rs @@ -413,6 +413,23 @@ pub async fn handler_internal( ); Err(Error::TenantSuspended) } + Error::ApnsCertificateExpired => { + let reason = "APNs certificate expired"; + state + .tenant_store + .suspend_tenant(&tenant_id, reason) + .await + .map_err(|e| (e, analytics.clone()))?; + increment_counter!(state.metrics, tenant_suspensions); + warn!( + %tenant_id, + client_id = %client_id, + notification_id = %notification.id, + push_type = client.push_type.as_str(), + "tenant has been suspended due to: {reason}" + ); + Err(Error::TenantSuspended) + } Error::BadFcmApiKey => { state .tenant_store diff --git a/src/providers/apns.rs b/src/providers/apns.rs index 709d4e4d..42b7a3ea 100644 --- a/src/providers/apns.rs +++ b/src/providers/apns.rs @@ -4,7 +4,7 @@ use { a2::{ErrorReason, NotificationBuilder, NotificationOptions}, async_trait::async_trait, std::io::Read, - tracing::{debug, instrument, warn}, + tracing::{debug, info, instrument, warn}, }; #[derive(Debug, Clone)] @@ -145,6 +145,21 @@ impl PushProvider for ApnsProvider { reason => Err(Error::ApnsResponse(reason)), }, }, + a2::Error::ConnectionError(ref hyper_error) => { + let dbg = format!("{hyper_error:?}"); + // e.g. Apns(ConnectionError(hyper::Error(Io, Custom { kind: InvalidData, error: "received fatal alert: CertificateExpired" }))) + if dbg.contains("received fatal alert: CertificateExpired") { + // Checking if debug fmt contains something is strange. + // Logging stuff here temporarily so we can determine better + // ways to detect this error (e.g. display). Ideally we can extract + // the error field directly and check if exactly equal to the above + // rather than using contains() + info!("APNs certificate expired: debug:{dbg}, display: {hyper_error}"); + Err(Error::ApnsCertificateExpired) + } else { + Err(Error::Apns(e)) + } + } e => Err(Error::Apns(e)), }, }