-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Basic autoendpoint extractors (#151)
* Add FromRequest impl for Metrics * Organize routes and add the rest of the health checks * Edit the /__lbheartbeat__ route to be a little clearer * Add a no-op webpush route * Add basic webpush extraction structs * Impl FromRequest for TokenInfo * Translate wip FromRequest impl for Subscription from Python validator Needs the meat of the validation + refactoring. Also unsure if the crypto_keys config is correct, as the Python version enforces brackets, even in env variables. * Extract extractors to new module and add some docs Pun fully intended. * Add some more docs and refactoring to Subscription FromRequest impl * Add a WebPushHeaders extractor and header utilities * Add validation to WebPushHeaders * Impl FromRequest for Notification Based on the Python validator/extractor. Kind of annoying that we have to `take` the payload because GATs isn't stable... Hopefully no one else needs it! * Fix incorrect call to get server state * Add extractors to webpush_route * Fix errors after rebase * Add error for invalid token * Make the subscription public key optional v1 may not have a public key * Suppress unused variable / mutability lint warnings * Be more explicit about what extractors need the payload * Add a PayloadError error kind to remove a TODO Related to #103 (needs more work before it can be closed).
- Loading branch information
1 parent
0677826
commit b08fdbd
Showing
15 changed files
with
400 additions
and
27 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
//! Actix extractors (`FromRequest`). These extractors transform and validate | ||
//! the incoming request data. | ||
pub mod notification; | ||
pub mod subscription; | ||
pub mod token_info; | ||
pub mod webpush_headers; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
use crate::error::{ApiError, ApiErrorKind}; | ||
use crate::server::extractors::subscription::Subscription; | ||
use crate::server::extractors::webpush_headers::WebPushHeaders; | ||
use actix_web::dev::{Payload, PayloadStream}; | ||
use actix_web::{FromRequest, HttpRequest}; | ||
use autopush_common::util::sec_since_epoch; | ||
use futures::{future, FutureExt, StreamExt}; | ||
|
||
/// Extracts notification data from `Subscription` and request data | ||
pub struct Notification { | ||
pub uaid: String, | ||
pub channel_id: String, | ||
pub version: String, | ||
pub ttl: Option<u64>, | ||
pub topic: Option<String>, | ||
pub timestamp: u64, | ||
pub data: String, | ||
} | ||
|
||
impl FromRequest for Notification { | ||
type Error = ApiError; | ||
type Future = future::LocalBoxFuture<'static, Result<Self, Self::Error>>; | ||
type Config = (); | ||
|
||
fn from_request(req: &HttpRequest, payload: &mut Payload<PayloadStream>) -> Self::Future { | ||
let req = req.clone(); | ||
let mut payload = payload.take(); | ||
|
||
async move { | ||
let headers = WebPushHeaders::extract(&req).await?; | ||
let subscription = Subscription::extract(&req).await?; | ||
|
||
// Read data and convert to base64 | ||
let mut data = Vec::new(); | ||
while let Some(item) = payload.next().await { | ||
data.extend_from_slice(&item.map_err(ApiErrorKind::PayloadError)?); | ||
} | ||
let data = base64::encode_config(data, base64::URL_SAFE_NO_PAD); | ||
|
||
Ok(Notification { | ||
uaid: subscription.uaid, | ||
channel_id: subscription.channel_id, | ||
version: subscription.api_version, | ||
ttl: headers.ttl, | ||
topic: headers.topic, | ||
timestamp: sec_since_epoch(), | ||
data, | ||
}) | ||
} | ||
.boxed_local() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
use crate::error::{ApiError, ApiErrorKind}; | ||
use crate::server::extractors::token_info::TokenInfo; | ||
use crate::server::ServerState; | ||
use actix_http::{Payload, PayloadStream}; | ||
use actix_web::web::Data; | ||
use actix_web::{FromRequest, HttpRequest}; | ||
use futures::future; | ||
|
||
/// Extracts subscription data from `TokenInfo` and verifies auth/crypto headers | ||
pub struct Subscription { | ||
pub uaid: String, | ||
pub channel_id: String, | ||
pub api_version: String, | ||
pub public_key: Option<String>, | ||
} | ||
|
||
impl FromRequest for Subscription { | ||
type Error = ApiError; | ||
type Future = future::Ready<Result<Self, Self::Error>>; | ||
type Config = (); | ||
|
||
fn from_request(req: &HttpRequest, _: &mut Payload<PayloadStream>) -> Self::Future { | ||
// Collect token info and server state | ||
let token_info = match TokenInfo::extract(req).into_inner() { | ||
Ok(t) => t, | ||
Err(e) => return future::err(e), | ||
}; | ||
let state: Data<ServerState> = Data::extract(req) | ||
.into_inner() | ||
.expect("No server state found"); | ||
let fernet = state.fernet.as_ref(); | ||
|
||
// Decrypt the token | ||
let token = match fernet.decrypt(&token_info.token) { | ||
Ok(t) => t, | ||
Err(_) => return future::err(ApiErrorKind::InvalidToken.into()), | ||
}; | ||
|
||
if token_info.api_version == "v1" && token.len() != 32 { | ||
// Corrupted token | ||
return future::err(ApiErrorKind::InvalidToken.into()); | ||
} | ||
|
||
// Extract public key | ||
let public_key = None; | ||
if let Some(_crypto_key_header) = token_info.crypto_key_header { | ||
todo!("Extract public key from header") | ||
} | ||
|
||
if let Some(_auth_header) = token_info.auth_header { | ||
todo!("Parse vapid auth") | ||
} | ||
|
||
// Validate key data if on v2 | ||
if token_info.api_version == "v2" { | ||
todo!("Perform v2 checks") | ||
} | ||
|
||
future::ok(Subscription { | ||
uaid: hex::encode(&token[..16]), | ||
channel_id: hex::encode(&token[16..32]), | ||
api_version: token_info.api_version, | ||
public_key, | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
use crate::error::ApiError; | ||
use crate::server::header_util::get_owned_header; | ||
use actix_http::{Payload, PayloadStream}; | ||
use actix_web::{FromRequest, HttpRequest}; | ||
use futures::future; | ||
|
||
/// Extracts basic token data from the webpush request path and headers | ||
pub struct TokenInfo { | ||
pub api_version: String, | ||
pub token: String, | ||
pub crypto_key_header: Option<String>, | ||
pub auth_header: Option<String>, | ||
} | ||
|
||
impl FromRequest for TokenInfo { | ||
type Error = ApiError; | ||
type Future = future::Ready<Result<Self, Self::Error>>; | ||
type Config = (); | ||
|
||
fn from_request(req: &HttpRequest, _: &mut Payload<PayloadStream>) -> Self::Future { | ||
// Path variables | ||
let api_version = req | ||
.match_info() | ||
.get("api_version") | ||
.unwrap_or("v1") | ||
.to_string(); | ||
let token = req | ||
.match_info() | ||
.get("token") | ||
.expect("{token} must be part of the webpush path") | ||
.to_string(); | ||
|
||
future::ok(TokenInfo { | ||
api_version, | ||
token, | ||
crypto_key_header: get_owned_header(req, "crypto-key"), | ||
auth_header: get_owned_header(req, "authorization"), | ||
}) | ||
} | ||
} |
Oops, something went wrong.