feat(docs): generate developer docs with rustdoc
This commit is contained in:
Родитель
fb56a2090b
Коммит
f2edb78c79
|
@ -4,11 +4,11 @@ version = "1.115.0"
|
|||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "fxa-email-service"
|
||||
name = "fxa_email_send"
|
||||
path = "src/bin/service.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "fxa-email-queues"
|
||||
name = "fxa_email_queues"
|
||||
path = "src/bin/queues.rs"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -16,8 +16,8 @@ RUN \
|
|||
cargo build --release && \
|
||||
cp -R /app/config/* /app/bin/config && \
|
||||
cp /app/Rocket.toml /app/bin && \
|
||||
cp /app/target/release/fxa-email-service /app/bin && \
|
||||
cp /app/target/release/fxa-email-queues /app/bin
|
||||
cp /app/target/release/fxa_email_send /app/bin && \
|
||||
cp /app/target/release/fxa_email_queues /app/bin
|
||||
|
||||
|
||||
FROM debian:stretch-slim
|
||||
|
@ -38,4 +38,4 @@ USER app
|
|||
|
||||
ENV ROCKET_ENV production
|
||||
|
||||
CMD ["/app/bin/fxa-email-service"]
|
||||
CMD ["/app/bin/fxa_email_send"]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# fxa-email-service
|
||||
# fxa_email_service
|
||||
|
||||
[![Build status](https://img.shields.io/travis/mozilla/fxa-email-service.svg?style=flat-square)](https://travis-ci.org/mozilla/fxa-email-service)
|
||||
[![CircleCI](https://circleci.com/gh/mozilla/fxa-email-service/tree/master.svg?style=svg)](https://circleci.com/gh/mozilla/fxa-email-service/tree/master)
|
||||
|
@ -194,7 +194,7 @@ Once you have config set,
|
|||
you can start the service with:
|
||||
|
||||
```
|
||||
cargo r --bin fxa-email-service
|
||||
cargo r --bin fxa_email_send
|
||||
```
|
||||
|
||||
Then you can use `curl`
|
||||
|
@ -228,7 +228,7 @@ or in `config/local.json`:
|
|||
Then start the service:
|
||||
|
||||
```
|
||||
cargo r --bin fxa-email-service
|
||||
cargo r --bin fxa_email_send
|
||||
```
|
||||
|
||||
Then set `provider` to `sendgrid` in your request payload:
|
||||
|
@ -308,5 +308,5 @@ to the main email-sending service.
|
|||
You can run it locally like so:
|
||||
|
||||
```
|
||||
cargo r --bin fxa-email-queues
|
||||
cargo r --bin fxa_email_queues
|
||||
```
|
||||
|
|
2
r
2
r
|
@ -1 +1 @@
|
|||
scripts/run_service.sh
|
||||
scripts/run_send.sh
|
|
@ -1,3 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
cargo run --bin fxa-email-queues
|
||||
cargo run --bin fxa_email_queues
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
cargo run --bin fxa_email_send
|
|
@ -1,3 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
cargo run --bin fxa-email-service
|
|
@ -2,6 +2,8 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Error definitions.
|
||||
|
||||
use std::{fmt, result};
|
||||
|
||||
use failure::{Backtrace, Context, Fail};
|
||||
|
@ -22,6 +24,14 @@ mod test;
|
|||
|
||||
pub type AppResult<T> = result::Result<T, AppError>;
|
||||
|
||||
/// The main error type
|
||||
/// returned by this service.
|
||||
///
|
||||
/// Error responses are serialised with a JSON body
|
||||
/// that honours the same format
|
||||
/// used by other FxA services:
|
||||
///
|
||||
/// `{ code, error, errno, message }`
|
||||
#[derive(Debug)]
|
||||
pub struct AppError {
|
||||
inner: Context<AppErrorKind>,
|
||||
|
|
|
@ -2,6 +2,20 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Strongly-typed wrapper
|
||||
//! for a subset of [`fxa-auth-db-mysql`][authdb].
|
||||
//!
|
||||
//! Ultimately we will move away
|
||||
//! from the auth db
|
||||
//! so, to avoid unnecessary coupling,
|
||||
//! this module MUST NOT be used directly.
|
||||
//! Instead,
|
||||
//! all access should be via
|
||||
//! [`bounces::Bounces`][bounces].
|
||||
//!
|
||||
//! [authdb]: https://github.com/mozilla/fxa-auth-db-mysql/
|
||||
//! [bounces]: ../bounces/struct.Bounces.html
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use hex;
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! The queue-processing loop for fxa_email_service.
|
||||
//!
|
||||
//! Configuration is via [`settings::Settings`][settings].
|
||||
//!
|
||||
//! [settings]: ../fxa_email_service/settings/struct.Settings.html
|
||||
|
||||
extern crate futures;
|
||||
extern crate fxa_email_service;
|
||||
#[macro_use]
|
||||
|
|
|
@ -2,6 +2,15 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! The main process for fxa-email-service.
|
||||
//! Starts a Rocket server
|
||||
//! that exposes one endpoint: `POST /send`
|
||||
//!
|
||||
//! Configuration is via [`settings::Settings`][settings].
|
||||
//! By default the server listens on `127.0.0.1:8001`.
|
||||
//!
|
||||
//! [settings]: ../fxa_email_service/settings/struct.Settings.html
|
||||
|
||||
#![feature(plugin)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
|
@ -46,7 +55,7 @@ fn main() {
|
|||
let log =
|
||||
MozlogLogger::with_request(request).expect("MozlogLogger::with_request error");
|
||||
if response.status().code == 200 {
|
||||
slog_info!(log, "{}", "Request finished succesfully.";
|
||||
slog_info!(log, "{}", "Request finished succesfully.";
|
||||
"status_code" => response.status().code, "status_msg" => response.status().reason);
|
||||
}
|
||||
}))
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Bounce and complaint handling.
|
||||
|
||||
use std::{collections::HashMap, time::SystemTime};
|
||||
|
||||
use app_errors::{AppErrorKind, AppResult};
|
||||
|
@ -12,6 +14,14 @@ use settings::{BounceLimit, BounceLimits, Settings};
|
|||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
/// Bounce/complaint registry.
|
||||
///
|
||||
/// Currently just a nicer abstraction
|
||||
/// over the `emailBounces` table
|
||||
/// in `fxa-auth-db-mysql`,
|
||||
/// but longer-term we'll migrate
|
||||
/// to something specifically tailored
|
||||
/// for this service.
|
||||
#[derive(Debug)]
|
||||
pub struct Bounces<D: Db> {
|
||||
db: D,
|
||||
|
@ -22,6 +32,7 @@ impl<D> Bounces<D>
|
|||
where
|
||||
D: Db,
|
||||
{
|
||||
/// Instantiate the registry.
|
||||
pub fn new(settings: &Settings, db: D) -> Bounces<D> {
|
||||
Bounces {
|
||||
db,
|
||||
|
@ -29,6 +40,15 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Check an email address
|
||||
/// against bounce/complaint records
|
||||
/// from the registry.
|
||||
///
|
||||
/// If matching records are found,
|
||||
/// they are checked against thresholds
|
||||
/// defined in the [`BounceLimits` setting][limits].
|
||||
///
|
||||
/// [limits]: ../settings/struct.BounceLimits.html
|
||||
pub fn check(&self, address: &str) -> AppResult<()> {
|
||||
let bounces = self.db.get_bounces(address)?;
|
||||
let now = SystemTime::now()
|
||||
|
@ -69,6 +89,8 @@ where
|
|||
.map(|_| ())
|
||||
}
|
||||
|
||||
/// Record a hard or soft bounce
|
||||
/// against an email address.
|
||||
pub fn record_bounce(
|
||||
&self,
|
||||
address: &str,
|
||||
|
@ -80,6 +102,8 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Record a complaint
|
||||
/// against an email address.
|
||||
pub fn record_complaint(
|
||||
&self,
|
||||
address: &str,
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Deserialization functions
|
||||
//! for use with serde's `deserialize_with` attribute.
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use serde::de::{Deserialize, Deserializer, Error, Unexpected};
|
||||
|
@ -12,6 +15,7 @@ use validate;
|
|||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
/// Validate and deserialize an AWS region.
|
||||
pub fn aws_region<'d, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'d>,
|
||||
|
@ -19,6 +23,7 @@ where
|
|||
deserialize(deserializer, validate::aws_region, "AWS region")
|
||||
}
|
||||
|
||||
/// Validate and deserialize an AWS access key.
|
||||
pub fn aws_access<'d, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'d>,
|
||||
|
@ -26,6 +31,7 @@ where
|
|||
deserialize(deserializer, validate::aws_access, "AWS access key")
|
||||
}
|
||||
|
||||
/// Validate and deserialize an AWS secret key.
|
||||
pub fn aws_secret<'d, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'d>,
|
||||
|
@ -33,6 +39,7 @@ where
|
|||
deserialize(deserializer, validate::aws_secret, "AWS secret key")
|
||||
}
|
||||
|
||||
/// Validate and deserialize a base URI.
|
||||
pub fn base_uri<'d, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'d>,
|
||||
|
@ -40,6 +47,7 @@ where
|
|||
deserialize(deserializer, validate::base_uri, "base URI")
|
||||
}
|
||||
|
||||
/// Validate and deserialize an email address.
|
||||
pub fn email_address<'d, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'d>,
|
||||
|
@ -48,6 +56,7 @@ where
|
|||
Ok(email.to_lowercase())
|
||||
}
|
||||
|
||||
/// Validate and deserialize a duration.
|
||||
pub fn duration<'d, D>(deserializer: D) -> Result<u64, D::Error>
|
||||
where
|
||||
D: Deserializer<'d>,
|
||||
|
@ -58,6 +67,7 @@ where
|
|||
.map_err(|_| D::Error::invalid_value(Unexpected::Str(&value), &"duration"))
|
||||
}
|
||||
|
||||
/// Validate and deserialize a host name or IP address.
|
||||
pub fn host<'d, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'d>,
|
||||
|
@ -65,6 +75,7 @@ where
|
|||
deserialize(deserializer, validate::host, "host name or IP address")
|
||||
}
|
||||
|
||||
/// Validate and deserialize a provider name.
|
||||
pub fn provider<'d, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'d>,
|
||||
|
@ -72,6 +83,7 @@ where
|
|||
deserialize(deserializer, validate::provider, "'ses' or 'sendgrid'")
|
||||
}
|
||||
|
||||
/// Validate and deserialize a sender name.
|
||||
pub fn sender_name<'d, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'d>,
|
||||
|
@ -79,6 +91,7 @@ where
|
|||
deserialize(deserializer, validate::sender_name, "sender name")
|
||||
}
|
||||
|
||||
/// Validate and deserialize a Sendgrid API key.
|
||||
pub fn sendgrid_api_key<'d, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'d>,
|
||||
|
@ -86,6 +99,7 @@ where
|
|||
deserialize(deserializer, validate::sendgrid_api_key, "Sendgrid API key")
|
||||
}
|
||||
|
||||
/// Validate and deserialize an AWS SQS URL.
|
||||
pub fn sqs_url<'d, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'d>,
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Maps duration strings to millisecond values.
|
||||
|
||||
use std::{
|
||||
convert::{From, TryFrom},
|
||||
error::Error,
|
||||
|
@ -28,6 +30,7 @@ lazy_static! {
|
|||
Regex::new("^(?:([0-9]+) )?(second|minute|hour|day|week|month|year)s?$").unwrap();
|
||||
}
|
||||
|
||||
/// The error type returned by `Duration::try_from`.
|
||||
#[derive(Debug)]
|
||||
pub struct DurationError {
|
||||
pub value: String,
|
||||
|
@ -45,6 +48,14 @@ impl Display for DurationError {
|
|||
}
|
||||
}
|
||||
|
||||
/// A duration type
|
||||
/// represented in milliseconds,
|
||||
/// for compatibility with
|
||||
/// the rest of the FxA ecosystem.
|
||||
///
|
||||
/// Can be deserialized from duration strings
|
||||
/// of the format `"{number} {period}"`,
|
||||
/// e.g. `"1 hour"` or `"10 minutes"`.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Duration(u64);
|
||||
|
||||
|
|
22
src/lib.rs
22
src/lib.rs
|
@ -2,6 +2,28 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! These are the developer docs
|
||||
//! for the Firefox Accounts email-sending service.
|
||||
//! For higher-level documentation,
|
||||
//! see the [readme].
|
||||
//!
|
||||
//! The project is compiled as a library
|
||||
//! that is linked against by
|
||||
//! two separate binaries:
|
||||
//!
|
||||
//! * [`fxa_email_send`][send] runs a Rocket server
|
||||
//! exposing an endpoint that enables callers
|
||||
//! to send email.
|
||||
//!
|
||||
//! * [`fxa_email_queues`][queues] runs a process
|
||||
//! that loops infinitely,
|
||||
//! polling SQS queues for
|
||||
//! SES bounce, complaint and delivery notifications.
|
||||
//!
|
||||
//! [readme]: https://github.com/mozilla/fxa-email-service/blob/master/README.md#fxa_email_service
|
||||
//! [send]: ../fxa_email_send/index.html
|
||||
//! [queues]: ../fxa_email_queues/index.html
|
||||
|
||||
#![feature(assoc_unix_epoch)]
|
||||
#![feature(plugin)]
|
||||
#![feature(try_from)]
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Mozlog-compatible logging.
|
||||
|
||||
use std::{io, ops::Deref};
|
||||
|
||||
use failure::{err_msg, Error};
|
||||
|
@ -65,9 +67,11 @@ impl Value for Settings {
|
|||
}
|
||||
}
|
||||
|
||||
/// Mozlog-compatible logger type.
|
||||
pub struct MozlogLogger(slog::Logger);
|
||||
|
||||
impl MozlogLogger {
|
||||
/// Construct a logger.
|
||||
pub fn new(settings: &Settings) -> Result<MozlogLogger, Error> {
|
||||
let logger = match &*settings.logging {
|
||||
"mozlog" => {
|
||||
|
@ -92,6 +96,7 @@ impl MozlogLogger {
|
|||
Ok(MozlogLogger(logger?))
|
||||
}
|
||||
|
||||
/// Log a rocket request.
|
||||
pub fn with_request(request: &Request) -> Result<MozlogLogger, Error> {
|
||||
let logger = request
|
||||
.guard::<State<MozlogLogger>>()
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Temporary storage for message metadata.
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{self, Display, Formatter},
|
||||
|
@ -16,6 +18,15 @@ use settings::Settings;
|
|||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
/// Message data store.
|
||||
///
|
||||
/// Currently uses Redis
|
||||
/// under the hood,
|
||||
/// although that may not
|
||||
/// always be the case.
|
||||
///
|
||||
/// Data is keyed by
|
||||
/// a hash of the message id.
|
||||
#[derive(Debug)]
|
||||
pub struct MessageData {
|
||||
client: RedisClient,
|
||||
|
@ -23,6 +34,7 @@ pub struct MessageData {
|
|||
}
|
||||
|
||||
impl MessageData {
|
||||
/// Instantiate a storage client.
|
||||
pub fn new(settings: &Settings) -> MessageData {
|
||||
MessageData {
|
||||
client: RedisClient::open(
|
||||
|
@ -32,6 +44,11 @@ impl MessageData {
|
|||
}
|
||||
}
|
||||
|
||||
/// Consume (read and delete) message metadata.
|
||||
///
|
||||
/// This is a destructive operation.
|
||||
/// Once consumed,
|
||||
/// the data is permanently destroyed.
|
||||
pub fn consume(&self, message_id: &str) -> Result<String, MessageDataError> {
|
||||
let key = self.generate_key(message_id)?;
|
||||
let key_str = key.as_str();
|
||||
|
@ -44,6 +61,10 @@ impl MessageData {
|
|||
.map_err(From::from)
|
||||
}
|
||||
|
||||
/// Store message metadata.
|
||||
///
|
||||
/// Any data previously stored for the message id
|
||||
/// will be replaced.
|
||||
pub fn set(&self, message_id: &str, metadata: &str) -> Result<(), MessageDataError> {
|
||||
let key = self.generate_key(message_id)?;
|
||||
self.client.set(key.as_str(), metadata).map_err(From::from)
|
||||
|
@ -56,6 +77,7 @@ impl MessageData {
|
|||
}
|
||||
}
|
||||
|
||||
/// The error type returned by `MessageData` methods.
|
||||
#[derive(Debug)]
|
||||
pub struct MessageDataError {
|
||||
description: String,
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Generic abstraction of specific email providers.
|
||||
|
||||
use std::{boxed::Box, collections::HashMap};
|
||||
|
||||
use self::{
|
||||
|
@ -14,6 +16,7 @@ mod mock;
|
|||
mod sendgrid;
|
||||
mod ses;
|
||||
|
||||
/// Email headers.
|
||||
pub type Headers = HashMap<String, String>;
|
||||
|
||||
trait Provider {
|
||||
|
@ -28,12 +31,14 @@ trait Provider {
|
|||
) -> AppResult<String>;
|
||||
}
|
||||
|
||||
/// Generic provider wrapper.
|
||||
pub struct Providers {
|
||||
default_provider: String,
|
||||
providers: HashMap<String, Box<Provider>>,
|
||||
}
|
||||
|
||||
impl Providers {
|
||||
/// Instantiate the provider clients.
|
||||
pub fn new(settings: &Settings) -> Providers {
|
||||
let mut providers: HashMap<String, Box<Provider>> = HashMap::new();
|
||||
|
||||
|
@ -53,6 +58,7 @@ impl Providers {
|
|||
}
|
||||
}
|
||||
|
||||
/// Send an email via an underlying provider.
|
||||
pub fn send(
|
||||
&self,
|
||||
to: &str,
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Queue-processing abstractions.
|
||||
|
||||
use std::{
|
||||
boxed::Box,
|
||||
error::Error,
|
||||
|
@ -24,6 +26,7 @@ pub mod sqs;
|
|||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
/// Top-level queue wrapper.
|
||||
#[derive(Debug)]
|
||||
pub struct Queues {
|
||||
bounce_queue: Box<Incoming>,
|
||||
|
@ -34,6 +37,7 @@ pub struct Queues {
|
|||
message_data: MessageData,
|
||||
}
|
||||
|
||||
/// An incoming bounce/complaint queue.
|
||||
pub trait Incoming: Debug + Sync {
|
||||
fn receive(&'static self) -> ReceiveFuture;
|
||||
fn delete(&'static self, message: Message) -> DeleteFuture;
|
||||
|
@ -42,27 +46,34 @@ pub trait Incoming: Debug + Sync {
|
|||
type ReceiveFuture = Box<Future<Item = Vec<Message>, Error = QueueError>>;
|
||||
type DeleteFuture = Box<Future<Item = (), Error = QueueError>>;
|
||||
|
||||
/// An outgoing notification queue.
|
||||
pub trait Outgoing: Debug + Sync {
|
||||
fn send(&'static self, body: &Notification) -> SendFuture;
|
||||
}
|
||||
|
||||
type SendFuture = Box<Future<Item = String, Error = QueueError>>;
|
||||
|
||||
/// A queue factory.
|
||||
pub trait Factory {
|
||||
fn new(id: String, settings: &Settings) -> Self;
|
||||
}
|
||||
|
||||
/// Generic message type.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Message {
|
||||
pub id: String,
|
||||
pub notification: Notification,
|
||||
}
|
||||
|
||||
/// The error type returned by queue methods.
|
||||
#[derive(Debug)]
|
||||
pub struct QueueError {
|
||||
description: String,
|
||||
}
|
||||
|
||||
/// Queue "ids"
|
||||
/// (which is really just a generic name
|
||||
/// for SQS queue URLs).
|
||||
#[derive(Debug)]
|
||||
pub struct QueueIds {
|
||||
pub bounce: String,
|
||||
|
@ -72,6 +83,7 @@ pub struct QueueIds {
|
|||
}
|
||||
|
||||
impl Queues {
|
||||
/// Instantiate the queue clients.
|
||||
pub fn new<Q: 'static>(ids: QueueIds, settings: &Settings) -> Queues
|
||||
where
|
||||
Q: Incoming + Outgoing + Factory,
|
||||
|
@ -86,6 +98,7 @@ impl Queues {
|
|||
}
|
||||
}
|
||||
|
||||
/// Poll all queues and handle any notifications.
|
||||
pub fn process(&'static self) -> QueueFuture {
|
||||
let joined_futures = self
|
||||
.process_queue(&self.bounce_queue)
|
||||
|
|
|
@ -2,18 +2,27 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Generic queue notification types.
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
pub use super::sqs::notification::{
|
||||
BounceSubtype, BounceType, ComplaintFeedbackType, Header, HeaderValue, NotificationType,
|
||||
};
|
||||
|
||||
// This "generic" notification type is actually just a subset of the SQS
|
||||
// notification type in src/queues/sqs/notification/mod.rs. That's mostly
|
||||
// so we can easily interface with existing auth server code that already
|
||||
// knows about the SQS message format. Longer-term we can do whatever we
|
||||
// want in here.
|
||||
|
||||
/// The root notification type.
|
||||
///
|
||||
/// This "generic" type
|
||||
/// is really just a subset
|
||||
/// of the [SQS notification type][sqs].
|
||||
/// That's mostly so we can easily interface
|
||||
/// with existing auth server code
|
||||
/// that already knows about
|
||||
/// the SQS message format.
|
||||
/// Longer-term we can do whatever we want
|
||||
/// in here.
|
||||
///
|
||||
/// [sqs]: ../sqs/notification/struct.Notification.html
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct Notification {
|
||||
#[serde(rename = "notificationType")]
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Concrete trait implementations
|
||||
//! for AWS SQS queues.
|
||||
|
||||
use std::{
|
||||
boxed::Box,
|
||||
fmt::{self, Debug, Formatter},
|
||||
|
@ -26,6 +29,7 @@ use settings::Settings;
|
|||
|
||||
pub mod notification;
|
||||
|
||||
/// An AWS SQS queue type.
|
||||
pub struct Queue {
|
||||
client: Box<Sqs>,
|
||||
url: String,
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! SQS queue notification types.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
|
@ -19,19 +21,22 @@ use auth_db::{BounceSubtype as AuthDbBounceSubtype, BounceType as AuthDbBounceTy
|
|||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
// This module is a direct encoding of the SES notification format documented
|
||||
// here:
|
||||
//
|
||||
// https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html
|
||||
//
|
||||
// It also receives synthesized events from our Sendgrid event proxy:
|
||||
//
|
||||
// https://github.com/mozilla/fxa-sendgrid-event-proxy
|
||||
//
|
||||
// Because we don't have all of the data to fill out an entire Notification
|
||||
// struct with the data that Sendgrid provides, many of the fields which are
|
||||
// not optional in the spec are Option-wrapped anyway.
|
||||
|
||||
/// The root SQS queue notification type.
|
||||
///
|
||||
/// This type is a direct encoding
|
||||
/// of the [SES notification format][format].
|
||||
///
|
||||
/// It also receives synthesized notifications
|
||||
/// from our [Sendgrid event proxy][proxy].
|
||||
/// Because we don't have all of the data
|
||||
/// necessary to fill out an entire `Notification`
|
||||
/// from the data that Sendgrid provides,
|
||||
/// many of the fields
|
||||
/// which are not optional in the spec
|
||||
/// are `Option`-wrapped anyway.
|
||||
///
|
||||
/// [format]: https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html
|
||||
/// [proxy]: https://github.com/mozilla/fxa-sendgrid-event-proxy
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Notification {
|
||||
#[serde(rename = "notificationType")]
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Route handler
|
||||
//! for the `POST /send` endpoint.
|
||||
|
||||
use rocket::{
|
||||
data::{self, FromData},
|
||||
http::Status,
|
||||
|
|
|
@ -2,11 +2,18 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Serialization functions
|
||||
//! for use with serde's `serialize_with` attribute.
|
||||
|
||||
use serde::ser;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
/// Serialize an `Option`
|
||||
/// containing sensitive data
|
||||
/// to either of the strings
|
||||
/// `"[hidden]"` or `"[not set]"`.
|
||||
pub fn hidden_or_not_set<T, S>(ref item: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: ser::Serializer,
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Application settings.
|
||||
|
||||
use std::env;
|
||||
|
||||
use config::{Config, ConfigError, Environment, File};
|
||||
|
@ -13,92 +15,200 @@ use serialize;
|
|||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
/// Settings related to `fxa-auth-db-mysql`,
|
||||
/// which is used to store
|
||||
/// bounce, complaint and delivery notifications.
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct AuthDb {
|
||||
/// The base URI for the `fxa-auth-db-mysql` instance.
|
||||
#[serde(deserialize_with = "deserialize::base_uri")]
|
||||
pub baseuri: String,
|
||||
}
|
||||
|
||||
/// Settings for AWS.
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Aws {
|
||||
/// Controls the access and secret keys for connecting to AWS.
|
||||
#[serde(serialize_with = "serialize::hidden_or_not_set")]
|
||||
pub keys: Option<AwsKeys>,
|
||||
|
||||
/// The AWS region for SES and SQS.
|
||||
#[serde(deserialize_with = "deserialize::aws_region")]
|
||||
pub region: String,
|
||||
|
||||
/// URLs for SQS queues.
|
||||
pub sqsurls: Option<SqsUrls>,
|
||||
}
|
||||
|
||||
/// AWS keys.
|
||||
/// These are sensitive data
|
||||
/// and will not be logged.
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct AwsKeys {
|
||||
/// The AWS access key.
|
||||
#[serde(deserialize_with = "deserialize::aws_access")]
|
||||
pub access: String,
|
||||
|
||||
/// The AWS secret key.
|
||||
#[serde(deserialize_with = "deserialize::aws_secret")]
|
||||
pub secret: String,
|
||||
}
|
||||
|
||||
/// A definition object for a bounce/complaint limit.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct BounceLimit {
|
||||
/// The time period
|
||||
/// within which to limit bounces/complaints.
|
||||
/// Deserialized from a string
|
||||
/// of the format `"{number} {period}"`,
|
||||
/// e.g. `"1 hour"` or `"10 minutes"`.
|
||||
#[serde(deserialize_with = "deserialize::duration")]
|
||||
pub period: u64,
|
||||
|
||||
/// The maximum number of bounces/complaints
|
||||
/// to permit within the specified time period.
|
||||
pub limit: u8,
|
||||
}
|
||||
|
||||
/// Controls the thresholds and behaviour
|
||||
/// for bounce and complaint reports.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct BounceLimits {
|
||||
/// Controls whether to enable bounce limits.
|
||||
/// If set to `false`,
|
||||
/// bounce and complaint records in the database
|
||||
/// are ignored.
|
||||
pub enabled: bool,
|
||||
|
||||
/// Limits for complaints/spam reports.
|
||||
pub complaint: Vec<BounceLimit>,
|
||||
|
||||
/// Limits for hard (permanent) bounces.
|
||||
pub hard: Vec<BounceLimit>,
|
||||
|
||||
/// Limits for soft (transient) bounces.
|
||||
pub soft: Vec<BounceLimit>,
|
||||
}
|
||||
|
||||
/// Settings for Redis.
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Redis {
|
||||
/// The host name or IP address.
|
||||
#[serde(deserialize_with = "deserialize::host")]
|
||||
pub host: String,
|
||||
|
||||
/// TCP port number.
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
/// Controls the name and email address
|
||||
/// that are used for the `From` and `Sender`
|
||||
/// email headers.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Sender {
|
||||
/// The email address.
|
||||
#[serde(deserialize_with = "deserialize::email_address")]
|
||||
pub address: String,
|
||||
|
||||
/// The name
|
||||
/// (may contain spaces).
|
||||
#[serde(deserialize_with = "deserialize::sender_name")]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Settings for Sendgrid.
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Sendgrid {
|
||||
/// The API key.
|
||||
/// This is sensitive data
|
||||
/// and will not be logged.
|
||||
#[serde(deserialize_with = "deserialize::sendgrid_api_key")]
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
/// URLs for SQS queues.
|
||||
/// Note that these are separate queues right now
|
||||
/// for consistency with the auth server.
|
||||
/// Long term,
|
||||
/// there is nothing preventing us
|
||||
/// from handling all incoming notification types
|
||||
/// with a single queue.
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct SqsUrls {
|
||||
// Queue URLs are specified here for consistency with the auth server.
|
||||
// However, we could also store queue names instead and then fetch the
|
||||
// URL with rusoto_sqs::GetQueueUrl. Then we might be allowed to include
|
||||
// the production queue names in default config?
|
||||
/// The incoming bounce queue URL.
|
||||
///
|
||||
/// Queue URLs are specified here
|
||||
/// for consistency with the auth server.
|
||||
/// However, we could also store queue names instead
|
||||
/// and then fetch the URL with rusoto_sqs::GetQueueUrl.
|
||||
/// Then we might be allowed to include
|
||||
/// the production queue names in default config?
|
||||
#[serde(deserialize_with = "deserialize::sqs_url")]
|
||||
pub bounce: String,
|
||||
|
||||
/// The incoming complaint queue URL.
|
||||
#[serde(deserialize_with = "deserialize::sqs_url")]
|
||||
pub complaint: String,
|
||||
|
||||
/// The incoming delivery queue URL.
|
||||
#[serde(deserialize_with = "deserialize::sqs_url")]
|
||||
pub delivery: String,
|
||||
|
||||
/// The outgoing notification queue URL,
|
||||
/// used to forward notifications
|
||||
/// for additional processing by callers.
|
||||
#[serde(deserialize_with = "deserialize::sqs_url")]
|
||||
pub notification: String,
|
||||
}
|
||||
|
||||
/// The root settings object.
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Settings {
|
||||
/// Settings related to `fxa-auth-db-mysql`,
|
||||
/// which is used to store
|
||||
/// bounce, complaint and delivery notifications.
|
||||
pub authdb: AuthDb,
|
||||
|
||||
/// Settings for AWS,
|
||||
/// including region, access keys
|
||||
/// and URLs for SQS queues.
|
||||
pub aws: Aws,
|
||||
|
||||
/// Controls the thresholds and behaviour
|
||||
/// for bounce and complaint reports.
|
||||
/// If bounce limits are enabled,
|
||||
/// emails sent to offending addresses
|
||||
/// will fail with a `429` error.
|
||||
pub bouncelimits: BounceLimits,
|
||||
|
||||
/// The HMAC key to use internally
|
||||
/// for hashing message ids.
|
||||
/// This is sensitive data
|
||||
/// and will not be logged.
|
||||
pub hmackey: String,
|
||||
|
||||
/// The logging format to use,
|
||||
/// can be `"mozlog"`, `"pretty"` or `"null"`.
|
||||
pub logging: String,
|
||||
|
||||
/// The default email provider to use,
|
||||
/// can be `"ses"`, `"sendgrid"` or `"mock"`.
|
||||
/// Note that this setting can be overridden
|
||||
/// on a per-request basis.
|
||||
#[serde(deserialize_with = "deserialize::provider")]
|
||||
pub provider: String,
|
||||
|
||||
/// Settings for Redis,
|
||||
/// which is used to store metadata
|
||||
/// associated with a message.
|
||||
pub redis: Redis,
|
||||
|
||||
/// Controls the name and email address
|
||||
/// that are used for the `From` and `Sender`
|
||||
/// email headers.
|
||||
pub sender: Sender,
|
||||
|
||||
/// Settings for Sendgrid.
|
||||
#[serde(serialize_with = "serialize::hidden_or_not_set")]
|
||||
pub sendgrid: Option<Sendgrid>,
|
||||
}
|
||||
|
|
|
@ -2,6 +2,17 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Common validation logic.
|
||||
//!
|
||||
//! All functions are predicates,
|
||||
//! returning `true` if a value is valid
|
||||
//! and `false` if it isn't.
|
||||
//! Note that the intention is not to provide
|
||||
//! perfect validation in each case.
|
||||
//! Mostly it is to rule out obvious mistakes
|
||||
//! when setting values in config
|
||||
//! or wiring in request parameters.
|
||||
|
||||
use regex::Regex;
|
||||
use rusoto_core::Region;
|
||||
|
||||
|
@ -26,42 +37,52 @@ lazy_static! {
|
|||
Regex::new("^https://sqs\\.[a-z0-9-]+\\.amazonaws\\.com/[0-9]+/[A-Za-z0-9-]+$").unwrap();
|
||||
}
|
||||
|
||||
/// Validate an AWS region.
|
||||
pub fn aws_region(value: &str) -> bool {
|
||||
value.parse::<Region>().is_ok()
|
||||
}
|
||||
|
||||
/// Validate an AWS access key.
|
||||
pub fn aws_access(value: &str) -> bool {
|
||||
AWS_ACCESS_FORMAT.is_match(value)
|
||||
}
|
||||
|
||||
/// Validate an AWS secret key.
|
||||
pub fn aws_secret(value: &str) -> bool {
|
||||
AWS_SECRET_FORMAT.is_match(value)
|
||||
}
|
||||
|
||||
/// Validate a base URI.
|
||||
pub fn base_uri(value: &str) -> bool {
|
||||
BASE_URI_FORMAT.is_match(value)
|
||||
}
|
||||
|
||||
/// Validate an email address.
|
||||
pub fn email_address(value: &str) -> bool {
|
||||
value.len() < 254 && EMAIL_ADDRESS_FORMAT.is_match(value)
|
||||
}
|
||||
|
||||
/// Validate a host name or IP address.
|
||||
pub fn host(value: &str) -> bool {
|
||||
HOST_FORMAT.is_match(value)
|
||||
}
|
||||
|
||||
/// Validate an email provider.
|
||||
pub fn provider(value: &str) -> bool {
|
||||
PROVIDER_FORMAT.is_match(value)
|
||||
}
|
||||
|
||||
/// Validate a sender name.
|
||||
pub fn sender_name(value: &str) -> bool {
|
||||
SENDER_NAME_FORMAT.is_match(value)
|
||||
}
|
||||
|
||||
/// Validate a Sendgrid API key.
|
||||
pub fn sendgrid_api_key(value: &str) -> bool {
|
||||
SENDGRID_API_KEY_FORMAT.is_match(value)
|
||||
}
|
||||
|
||||
/// Validate an AWS SQS URL.
|
||||
pub fn sqs_url(value: &str) -> bool {
|
||||
SQS_URL_FORMAT.is_match(value)
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче