#![deny(missing_docs)]
use std::time::Duration;
#[cfg(any(feature = "client", feature = "server"))]
use actix_http::{
header::{HeaderMap, ToStrError},
uri::PathAndQuery,
Method,
};
#[cfg(any(feature = "client", feature = "server"))]
use std::collections::BTreeMap;
#[cfg(feature = "client")]
mod sign;
#[cfg(feature = "digest")]
pub mod digest;
#[cfg(feature = "client")]
pub mod create;
#[cfg(feature = "server")]
pub mod middleware;
pub use http_signature_normalization::RequiredError;
pub mod prelude {
pub use crate::{Config, RequiredError};
#[cfg(feature = "client")]
pub use crate::{PrepareSignError, Sign};
#[cfg(feature = "server")]
pub use crate::{
middleware::{SignatureVerified, VerifySignature},
verify::{Algorithm, DeprecatedAlgorithm, Unverified},
PrepareVerifyError, SignatureVerify,
};
#[cfg(all(feature = "digest", feature = "client"))]
pub use crate::digest::{DigestClient, DigestCreate, SignExt};
#[cfg(all(feature = "digest", feature = "server"))]
pub use crate::digest::{
middleware::{DigestVerified, VerifyDigest},
DigestPart, DigestVerify,
};
pub use actix_http::header::{InvalidHeaderValue, ToStrError};
}
#[cfg(feature = "server")]
pub mod verify {
pub use http_signature_normalization::verify::{
Algorithm, DeprecatedAlgorithm, ParseSignatureError, ParsedHeader, Unvalidated, Unverified,
ValidateError,
};
}
#[cfg(feature = "client")]
pub use self::client::{PrepareSignError, Sign};
#[cfg(feature = "server")]
pub use self::server::{PrepareVerifyError, SignatureVerify};
#[derive(Clone, Debug, Default)]
pub struct Config<Spawner = DefaultSpawner> {
config: http_signature_normalization::Config,
set_host: bool,
set_date: bool,
spawner: Spawner,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct DefaultSpawner;
#[derive(Debug)]
pub struct Canceled;
impl std::fmt::Display for Canceled {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Operation was canceled")
}
}
impl std::error::Error for Canceled {}
pub trait Spawn {
type Future<T>: std::future::Future<Output = Result<T, Canceled>>;
fn spawn_blocking<Func, Out>(&self, func: Func) -> Self::Future<Out>
where
Func: FnOnce() -> Out + Send + 'static,
Out: Send + 'static;
}
pub struct DefaultSpawnerFuture<Out> {
inner: actix_rt::task::JoinHandle<Out>,
}
impl Spawn for DefaultSpawner {
type Future<T> = DefaultSpawnerFuture<T>;
fn spawn_blocking<Func, Out>(&self, func: Func) -> Self::Future<Out>
where
Func: FnOnce() -> Out + Send + 'static,
Out: Send + 'static,
{
DefaultSpawnerFuture {
inner: actix_rt::task::spawn_blocking(func),
}
}
}
impl<Out> std::future::Future for DefaultSpawnerFuture<Out> {
type Output = Result<Out, Canceled>;
fn poll(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
let res = std::task::ready!(std::pin::Pin::new(&mut self.inner).poll(cx));
std::task::Poll::Ready(res.map_err(|_| Canceled))
}
}
#[cfg(feature = "client")]
mod client {
use super::{Config, RequiredError, Spawn};
use actix_http::header::{InvalidHeaderValue, ToStrError};
use actix_rt::task::JoinError;
use std::{fmt::Display, future::Future, pin::Pin};
pub trait Sign {
fn authorization_signature<F, E, K, S>(
self,
config: Config<S>,
key_id: K,
f: F,
) -> Pin<Box<dyn Future<Output = Result<Self, E>>>>
where
F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<JoinError>
+ From<PrepareSignError>
+ From<crate::Canceled>
+ From<InvalidHeaderValue>
+ std::fmt::Debug
+ Send
+ 'static,
K: Display + 'static,
S: Spawn + 'static,
Self: Sized;
fn signature<F, E, K, S>(
self,
config: Config<S>,
key_id: K,
f: F,
) -> Pin<Box<dyn Future<Output = Result<Self, E>>>>
where
F: FnOnce(&str) -> Result<String, E> + Send + 'static,
E: From<JoinError>
+ From<PrepareSignError>
+ From<crate::Canceled>
+ From<InvalidHeaderValue>
+ std::fmt::Debug
+ Send
+ 'static,
K: Display + 'static,
S: Spawn + 'static,
Self: Sized;
}
#[derive(Debug)]
pub enum PrepareSignError {
Header(ToStrError),
RequiredError(RequiredError),
Host(String),
InvalidHeader(actix_http::header::InvalidHeaderValue),
}
impl std::fmt::Display for PrepareSignError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Header(_) => f.write_str("Failed to read header"),
Self::RequiredError(_) => f.write_str("Missing required header"),
Self::Host(s) => write!(f, "No host provided for URL, {s}"),
Self::InvalidHeader(_) => f.write_str("Failed to set header"),
}
}
}
impl std::error::Error for PrepareSignError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Header(h) => Some(h),
Self::RequiredError(r) => Some(r),
Self::Host(_) => None,
Self::InvalidHeader(i) => Some(i),
}
}
}
impl From<ToStrError> for PrepareSignError {
fn from(value: ToStrError) -> Self {
Self::Header(value)
}
}
impl From<RequiredError> for PrepareSignError {
fn from(value: RequiredError) -> Self {
Self::RequiredError(value)
}
}
impl From<actix_http::header::InvalidHeaderValue> for PrepareSignError {
fn from(value: actix_http::header::InvalidHeaderValue) -> Self {
Self::InvalidHeader(value)
}
}
}
#[cfg(feature = "server")]
mod server {
use super::RequiredError;
use actix_http::header::ToStrError;
use std::future::Future;
pub trait SignatureVerify {
type Error: actix_web::ResponseError;
type Future: Future<Output = Result<bool, Self::Error>>;
fn signature_verify(
&mut self,
algorithm: Option<super::verify::Algorithm>,
key_id: String,
signature: String,
signing_string: String,
) -> Self::Future;
}
#[derive(Debug)]
pub enum PrepareVerifyError {
Missing,
Expired(String),
ParseField(&'static str),
Header(ToStrError),
Required(RequiredError),
}
impl std::fmt::Display for PrepareVerifyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Missing => f.write_str("Header is missing"),
Self::Expired(e) => write!(f, "Header is expired, {e}"),
Self::ParseField(field) => write!(f, "Couldn't parse required field, {field}"),
Self::Header(_) => f.write_str("Failed to read header"),
Self::Required(_) => f.write_str("Missing required header"),
}
}
}
impl std::error::Error for PrepareVerifyError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Missing | Self::Expired(_) | Self::ParseField(_) => None,
Self::Header(h) => Some(h),
Self::Required(r) => Some(r),
}
}
}
impl From<ToStrError> for PrepareVerifyError {
fn from(value: ToStrError) -> Self {
Self::Header(value)
}
}
impl From<RequiredError> for PrepareVerifyError {
fn from(value: RequiredError) -> Self {
Self::Required(value)
}
}
impl From<http_signature_normalization::PrepareVerifyError> for PrepareVerifyError {
fn from(e: http_signature_normalization::PrepareVerifyError) -> Self {
use http_signature_normalization as hsn;
match e {
hsn::PrepareVerifyError::Parse(parse_error) => {
PrepareVerifyError::ParseField(parse_error.missing_field())
}
hsn::PrepareVerifyError::Validate(validate_error) => match validate_error {
hsn::verify::ValidateError::Missing => PrepareVerifyError::Missing,
e @ hsn::verify::ValidateError::Expired { .. } => {
PrepareVerifyError::Expired(e.to_string())
}
},
hsn::PrepareVerifyError::Required(required_error) => {
PrepareVerifyError::Required(required_error)
}
}
}
}
impl actix_web::ResponseError for super::Canceled {
fn status_code(&self) -> actix_http::StatusCode {
actix_http::StatusCode::INTERNAL_SERVER_ERROR
}
fn error_response(&self) -> actix_web::HttpResponse<actix_http::body::BoxBody> {
actix_web::HttpResponse::new(self.status_code())
}
}
}
impl Config {
pub fn new() -> Self {
Config::default()
}
}
impl<Spawner> Config<Spawner> {
pub fn set_host_header(self) -> Self {
Config {
config: self.config,
set_host: true,
set_date: self.set_date,
spawner: self.spawner,
}
}
#[cfg(feature = "client")]
pub fn spawner<S>(self, spawner: S) -> Config<S>
where
S: Spawn,
{
Config {
config: self.config,
set_host: self.set_host,
set_date: self.set_date,
spawner,
}
}
pub fn mastodon_compat(self) -> Self {
Config {
config: self.config.mastodon_compat(),
set_host: true,
set_date: true,
spawner: self.spawner,
}
}
pub fn require_digest(self) -> Self {
Config {
config: self.config.require_digest(),
set_host: self.set_host,
set_date: self.set_date,
spawner: self.spawner,
}
}
pub fn dont_use_created_field(self) -> Self {
Config {
config: self.config.dont_use_created_field(),
set_host: self.set_host,
set_date: self.set_date,
spawner: self.spawner,
}
}
pub fn set_expiration(self, expires_after: Duration) -> Self {
Config {
config: self.config.set_expiration(expires_after),
set_host: self.set_host,
set_date: self.set_date,
spawner: self.spawner,
}
}
pub fn require_header(self, header: &str) -> Self {
Config {
config: self.config.require_header(header),
set_host: self.set_host,
set_date: self.set_date,
spawner: self.spawner,
}
}
#[cfg(feature = "client")]
pub fn begin_sign(
&self,
method: &Method,
path_and_query: Option<&PathAndQuery>,
headers: HeaderMap,
) -> Result<self::create::Unsigned, PrepareSignError> {
let headers = headers
.iter()
.map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string())))
.collect::<Result<BTreeMap<_, _>, ToStrError>>()?;
let path_and_query = path_and_query
.map(|p| p.to_string())
.unwrap_or_else(|| "/".to_string());
let unsigned = self
.config
.begin_sign(method.as_ref(), &path_and_query, headers)?;
Ok(self::create::Unsigned { unsigned })
}
#[cfg(feature = "server")]
pub fn begin_verify(
&self,
method: &Method,
path_and_query: Option<&PathAndQuery>,
headers: HeaderMap,
) -> Result<self::verify::Unverified, PrepareVerifyError> {
let headers = headers
.iter()
.map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string())))
.collect::<Result<BTreeMap<_, _>, ToStrError>>()?;
let path_and_query = path_and_query
.map(|p| p.to_string())
.unwrap_or_else(|| "/".to_string());
let unverified = self
.config
.begin_verify(method.as_ref(), &path_and_query, headers)?;
Ok(unverified)
}
}