#![deny(trivial_casts, trivial_numeric_casts, unused_extern_crates, unused_qualifications)]
#![warn(
missing_debug_implementations,
missing_docs,
unused_import_braces,
dead_code,
clippy::unwrap_used,
clippy::expect_used,
clippy::missing_docs_in_private_items,
clippy::missing_panics_doc
)]
pub mod config;
use std::{collections::HashMap, fmt, time::SystemTime};
use jsonwebtoken::{encode, DecodingKey, Header};
pub use jsonwebtoken::{Algorithm, EncodingKey};
use serde::{Deserialize, Serialize, Serializer};
pub enum Error {
JWT(jsonwebtoken::errors::Error),
Serde(serde_json::Error),
}
impl From<jsonwebtoken::errors::Error> for Error {
fn from(err: jsonwebtoken::errors::Error) -> Self {
Self::JWT(err)
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Self::Serde(err)
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::JWT(e) => e.fmt(f),
Error::Serde(e) => e.fmt(f),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::JWT(e) => e.fmt(f),
Error::Serde(e) => e.fmt(f),
}
}
}
pub fn make_encoding_key(
key: &[u8],
algorithm: Algorithm,
) -> jsonwebtoken::errors::Result<EncodingKey> {
match algorithm {
Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => Ok(EncodingKey::from_secret(key)),
Algorithm::ES256 | Algorithm::ES384 => EncodingKey::from_ec_pem(key),
Algorithm::RS256
| Algorithm::RS384
| Algorithm::RS512
| Algorithm::PS256
| Algorithm::PS384
| Algorithm::PS512 => EncodingKey::from_rsa_pem(key),
Algorithm::EdDSA => EncodingKey::from_ed_pem(key),
}
}
pub fn make_decoding_key(
key: &[u8],
algorithm: Algorithm,
) -> jsonwebtoken::errors::Result<DecodingKey> {
match algorithm {
Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => Ok(DecodingKey::from_secret(key)),
Algorithm::ES256 | Algorithm::ES384 => DecodingKey::from_ec_pem(key),
Algorithm::RS256
| Algorithm::RS384
| Algorithm::RS512
| Algorithm::PS256
| Algorithm::PS384
| Algorithm::PS512 => DecodingKey::from_rsa_pem(key),
Algorithm::EdDSA => DecodingKey::from_ed_pem(key),
}
}
#[derive(Debug, Clone)]
pub struct PassportBuilder {
certificate_url: String,
claims: PassportClaims,
expires_in: Option<u64>,
}
impl PassportBuilder {
pub fn new(certificate_url: String, origin: Identity) -> Self {
Self { certificate_url, claims: PassportClaims::new(origin), expires_in: None }
}
pub fn add_media_key(mut self, key: MediaKey) -> Self {
self.claims = self.claims.add_media_key(key);
self
}
pub fn add_destination(mut self, identity: Identity) -> Self {
self.claims = self.claims.add_destination(identity);
self
}
pub fn set_expires_in(mut self, expires_in: Option<u64>) -> Self {
self.expires_in = expires_in;
self
}
pub fn encode(mut self, key: &EncodingKey, algorithm: Algorithm) -> Result<String, Error> {
let header = Header {
typ: Some(String::from("passport")),
alg: algorithm,
cty: None,
jku: None,
kid: None,
x5u: Some(self.certificate_url.clone()),
x5t: None,
jwk: None,
x5c: None,
x5t_s256: None,
};
self.claims = self.claims.set_issuing_time(self.expires_in);
Ok(encode(&header, &self.claims, key)?)
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PassportClaims {
#[serde(default, skip_serializing_if = "HashMap::is_empty", rename = "dest")]
pub destination: HashMap<IdentityForms, Vec<String>>,
#[serde(rename = "orig")]
pub origin: Identity,
#[serde(rename = "iat")]
pub issued_at: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none", rename = "exp")]
pub expires_at: Option<u64>,
#[serde(default, skip_serializing_if = "Vec::is_empty", rename = "mky")]
pub media_keys: Vec<MediaKey>,
}
impl PassportClaims {
fn new(origin: Identity) -> Self {
Self {
destination: HashMap::new(),
origin,
issued_at: None,
expires_at: None,
media_keys: Vec::new(),
}
}
fn add_media_key(mut self, key: MediaKey) -> Self {
self.media_keys.push(key);
self
}
fn add_destination(mut self, identity: Identity) -> Self {
let inner = identity.clone().into_inner();
let key = IdentityForms::from(&identity);
self.destination.entry(key).or_insert_with(Vec::new).push(inner);
self
}
fn set_issuing_time(mut self, expires_in: Option<u64>) -> Self {
#[allow(clippy::expect_used)]
let now = SystemTime::UNIX_EPOCH.elapsed().expect("System time is before unix epoch").as_secs();
self.issued_at = Some(now);
self.expires_at = expires_in.map(|t| t + now);
self
}
}
#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug, Clone, Copy)]
pub enum IdentityForms {
#[serde(rename = "tn")]
TelephoneNumber,
#[serde(rename = "uri")]
URI,
}
#[derive(Deserialize, Clone, Debug)]
pub enum Identity {
#[serde(rename = "tn")]
TelephoneNumber(String),
#[serde(rename = "uri")]
URI(String),
}
impl Serialize for Identity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut data: HashMap<IdentityForms, String> = HashMap::new();
data.insert(IdentityForms::from(self), self.clone().into_inner());
serializer.serialize_newtype_struct("Identity", &data)
}
}
impl From<&Identity> for IdentityForms {
fn from(identity: &Identity) -> Self {
match identity {
Identity::TelephoneNumber(_) => IdentityForms::TelephoneNumber,
Identity::URI(_) => IdentityForms::URI,
}
}
}
impl Identity {
fn into_inner(self) -> String {
match self {
Self::TelephoneNumber(num) => num,
Self::URI(uri) => uri,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MediaKey {
algorithm: String,
digest: String,
}