use crate::errors::GenericError;
#[cfg(feature = "v1")]
use crate::errors::RsaKeyErrors;
#[cfg(all(not(feature = "v2"), feature = "v1"))]
use crate::v1::local_paseto as V1Local;
#[cfg(feature = "v1")]
use crate::v1::public_paseto as V1Public;
#[cfg(feature = "v2")]
use crate::v2::{local_paseto as V2Local, public_paseto as V2Public};
#[cfg(feature = "easy_tokens_chrono")]
use chrono::prelude::*;
use failure::Error;
#[cfg(feature = "v2")]
use ring::signature::Ed25519KeyPair;
#[cfg(feature = "v1")]
use ring::signature::RsaKeyPair;
use serde_json::{json, to_string, Value};
#[cfg(feature = "easy_tokens_time")]
use time::OffsetDateTime;
use std::collections::HashMap;
pub struct PasetoBuilder<'a> {
footer: Option<&'a str>,
encryption_key: Option<&'a [u8]>,
#[cfg(feature = "v1")]
rsa_key: Option<&'a [u8]>,
#[cfg(feature = "v2")]
ed_key: Option<&'a Ed25519KeyPair>,
extra_claims: HashMap<&'a str, Value>,
}
impl<'a> PasetoBuilder<'a> {
pub fn new() -> PasetoBuilder<'a> {
PasetoBuilder {
footer: None,
encryption_key: None,
#[cfg(feature = "v1")]
rsa_key: None,
#[cfg(feature = "v2")]
ed_key: None,
extra_claims: HashMap::new(),
}
}
pub fn build(&self) -> Result<String, Error> {
let strd_msg = to_string(&self.extra_claims)?;
#[cfg(feature = "v2")]
{
if let Some(mut enc_key) = self.encryption_key {
return V2Local(&strd_msg, self.footer.as_deref(), &mut enc_key);
}
}
#[cfg(all(not(feature = "v2"), feature = "v1"))]
{
if let Some(mut enc_key) = self.encryption_key {
return V1Local(&strd_msg, self.footer.as_deref(), &mut enc_key);
}
}
#[cfg(feature = "v2")]
{
if let Some(ed_key_pair) = self.ed_key {
return V2Public(&strd_msg, self.footer.as_deref(), &ed_key_pair);
}
}
#[cfg(feature = "v1")]
{
if let Some(the_rsa_key) = self.rsa_key {
let key_pair = RsaKeyPair::from_der(&the_rsa_key);
if key_pair.is_err() {
return Err(RsaKeyErrors::InvalidKey {})?;
}
let mut key_pair = key_pair.unwrap();
return V1Public(&strd_msg, self.footer.as_deref(), &mut key_pair);
}
}
return Err(GenericError::NoKeyProvided {})?;
}
}
#[cfg(feature = "v1")]
impl<'a> PasetoBuilder<'a> {
pub fn set_rsa_key(&'a mut self, private_key_der: &'a [u8]) -> &'a mut Self {
self.rsa_key = Some(private_key_der);
self
}
}
#[cfg(feature = "v2")]
impl<'a> PasetoBuilder<'a> {
pub fn set_ed25519_key(&'a mut self, key_pair: &'a Ed25519KeyPair) -> &'a mut Self {
self.ed_key = Some(key_pair);
self
}
}
impl<'a> PasetoBuilder<'a> {
pub fn set_encryption_key(&'a mut self, encryption_key: &'a [u8]) -> &'a mut Self {
self.encryption_key = Some(encryption_key);
self
}
pub fn set_footer(&'a mut self, footer: &'a str) -> &'a mut Self {
self.footer = Some(footer);
self
}
pub fn set_claim(&'a mut self, key: &'a str, value: Value) -> &'a mut Self {
self.extra_claims.insert(key, value);
self
}
pub fn set_audience(&'a mut self, audience: &str) -> &'a mut Self {
self.set_claim("aud", json!(audience))
}
#[cfg(all(feature = "easy_tokens_chrono", not(feature = "easy_tokens_time")))]
pub fn set_expiration(&'a mut self, expiration: &DateTime<Utc>) -> &'a mut Self {
self.set_claim("exp", json!(expiration))
}
#[cfg(all(feature = "easy_tokens_time", not(feature = "easy_tokens_chrono")))]
pub fn set_expiration(&'a mut self, expiration: &OffsetDateTime) -> &'a mut Self {
self.set_claim("exp", json!(expiration))
}
#[cfg(all(feature = "easy_tokens_chrono", feature = "easy_tokens_time"))]
pub fn set_expiration_chrono(&'a mut self, expiration: &DateTime<Utc>) -> &'a mut Self {
self.set_claim("exp", json!(expiration))
}
#[cfg(all(feature = "easy_tokens_time", feature = "easy_tokens_time"))]
pub fn set_expiration_time(&'a mut self, expiration: &OffsetDateTime) -> &'a mut Self {
self.set_claim("exp", json!(expiration))
}
#[cfg(all(feature = "easy_tokens_chrono", not(feature = "easy_tokens_time")))]
pub fn set_issued_at(&'a mut self, issued_at: Option<DateTime<Utc>>) -> &'a mut Self {
self.set_claim("iat", json!(issued_at.unwrap_or(Utc::now())))
}
#[cfg(all(feature = "easy_tokens_time", not(feature = "easy_tokens_chrono")))]
pub fn set_issued_at(&'a mut self, issued_at: Option<OffsetDateTime>) -> &'a mut Self {
self.set_claim("iat", json!(issued_at.unwrap_or(OffsetDateTime::now_utc())))
}
#[cfg(all(feature = "easy_tokens_chrono", feature = "easy_tokens_time"))]
pub fn set_issued_at_chrono(&'a mut self, issued_at: Option<DateTime<Utc>>) -> &'a mut Self {
self.set_claim("iat", json!(issued_at.unwrap_or(Utc::now())))
}
#[cfg(all(feature = "easy_tokens_time", feature = "easy_tokens_time"))]
pub fn set_issued_at_time(&'a mut self, issued_at: Option<OffsetDateTime>) -> &'a mut Self {
self.set_claim("iat", json!(issued_at.unwrap_or(OffsetDateTime::now_utc())))
}
pub fn set_issuer(&'a mut self, issuer: &str) -> &'a mut Self {
self.set_claim("iss", json!(issuer))
}
pub fn set_jti(&'a mut self, id: &str) -> &'a mut Self {
self.set_claim("jti", json!(id))
}
#[cfg(all(feature = "easy_tokens_chrono", not(feature = "easy_tokens_time")))]
pub fn set_not_before(&'a mut self, not_before: &DateTime<Utc>) -> &'a mut Self {
self.set_claim("nbf", json!(not_before))
}
#[cfg(all(feature = "easy_tokens_time", not(feature = "easy_tokens_chrono")))]
pub fn set_not_before(&'a mut self, not_before: &OffsetDateTime) -> &'a mut Self {
self.set_claim("nbf", json!(not_before))
}
#[cfg(all(feature = "easy_tokens_chrono", feature = "easy_tokens_time"))]
pub fn set_not_before_chrono(&'a mut self, not_before: &DateTime<Utc>) -> &'a mut Self {
self.set_claim("nbf", json!(not_before))
}
#[cfg(all(feature = "easy_tokens_time", feature = "easy_tokens_time"))]
pub fn set_not_before_time(&'a mut self, not_before: &OffsetDateTime) -> &'a mut Self {
self.set_claim("nbf", json!(not_before))
}
pub fn set_subject(&'a mut self, subject: &str) -> &'a mut Self {
self.set_claim("sub", json!(subject))
}
}
#[cfg(test)]
mod unit_test {
#[cfg(feature = "v2")]
use {super::*, crate::v2::local::decrypt_paseto as V2Decrypt, serde_json::from_str as ParseJson};
#[test]
#[cfg(all(feature = "v2", feature = "easy_tokens_chrono", not(feature = "easy_tokens_time")))]
fn can_construct_a_token_chrono() {
let token = PasetoBuilder::new()
.set_encryption_key(&Vec::from("YELLOW SUBMARINE, BLACK WIZARDRY".as_bytes()))
.set_issued_at(None)
.set_expiration(&Utc::now())
.set_issuer("issuer")
.set_audience("audience")
.set_jti("jti")
.set_not_before(&Utc::now())
.set_subject("test")
.set_claim("claim", json!("data"))
.set_footer("footer")
.build()
.expect("Failed to construct paseto token w/ builder!");
let decrypted_token = V2Decrypt(
&token,
Some("footer"),
&mut Vec::from("YELLOW SUBMARINE, BLACK WIZARDRY".as_bytes()),
)
.expect("Failed to decrypt token constructed with builder!");
let parsed: Value = ParseJson(&decrypted_token).expect("Failed to parse finalized token as json!");
assert!(parsed.get("iat").is_some());
assert!(parsed.get("iss").is_some());
assert!(parsed.get("aud").is_some());
assert!(parsed.get("jti").is_some());
assert!(parsed.get("sub").is_some());
assert!(parsed.get("claim").is_some());
assert!(parsed.get("nbf").is_some());
}
#[test]
#[cfg(all(feature = "v2", feature = "easy_tokens_time", not(feature = "easy_tokens_chrono")))]
fn can_construct_a_token_time() {
let token = PasetoBuilder::new()
.set_encryption_key(&Vec::from("YELLOW SUBMARINE, BLACK WIZARDRY".as_bytes()))
.set_issued_at(None)
.set_expiration(&OffsetDateTime::now_utc())
.set_issuer("issuer")
.set_audience("audience")
.set_jti("jti")
.set_not_before(&OffsetDateTime::now_utc())
.set_subject("test")
.set_claim("claim", json!("data"))
.set_footer("footer")
.build()
.expect("Failed to construct paseto token w/ builder!");
let decrypted_token = V2Decrypt(
&token,
Some("footer"),
&mut Vec::from("YELLOW SUBMARINE, BLACK WIZARDRY".as_bytes()),
)
.expect("Failed to decrypt token constructed with builder!");
let parsed: Value = ParseJson(&decrypted_token).expect("Failed to parse finalized token as json!");
assert!(parsed.get("iat").is_some());
assert!(parsed.get("iss").is_some());
assert!(parsed.get("aud").is_some());
assert!(parsed.get("jti").is_some());
assert!(parsed.get("sub").is_some());
assert!(parsed.get("claim").is_some());
assert!(parsed.get("nbf").is_some());
}
}