use std::time::{Duration, SystemTime, UNIX_EPOCH};
use serde::Serialize;
use uuid::Uuid;
use crate::aead::seal;
use crate::encoding::{encode_into, encoded_len};
use crate::error::MintError;
use crate::key::MandateKey;
use crate::serial;
use crate::types::{Alg, Encoding, NumericDate, MANIFEST_KEY};
pub struct Issuer {
key: MandateKey,
mandate_alg: Alg,
manifest_alg: Alg,
encoding: Encoding,
}
impl Issuer {
pub fn new(key: MandateKey) -> Self {
Issuer {
key,
mandate_alg: Alg::Siv,
manifest_alg: Alg::Siv,
encoding: Encoding::B64,
}
}
pub fn alg(mut self, alg: Alg) -> Self {
self.mandate_alg = alg;
self
}
pub fn manifest_alg(mut self, alg: Alg) -> Self {
self.manifest_alg = alg;
self
}
pub fn encoding(mut self, encoding: Encoding) -> Self {
self.encoding = encoding;
self
}
pub fn clauses<'a, T: Serialize>(&'a self, app: &'a T) -> MintBuilder<'a, T> {
MintBuilder {
issuer: self,
app,
exp: None,
tid: None,
iss: None,
aud: None,
sub: None,
manifest_plain: None,
}
}
}
pub struct MintBuilder<'a, T> {
issuer: &'a Issuer,
app: &'a T,
exp: Option<NumericDate>,
tid: Option<Uuid>,
iss: Option<String>,
aud: Option<Vec<String>>,
sub: Option<String>,
manifest_plain: Option<Result<Vec<u8>, MintError>>,
}
impl<'a, T: Serialize> MintBuilder<'a, T> {
pub fn exp(mut self, exp: NumericDate) -> Self {
self.exp = Some(exp);
self
}
pub fn expires_in(mut self, ttl: Duration) -> Self {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as NumericDate)
.unwrap_or(0);
let ttl = NumericDate::try_from(ttl.as_secs()).unwrap_or(NumericDate::MAX);
self.exp = Some(now.saturating_add(ttl));
self
}
pub fn tid(mut self, tid: Uuid) -> Self {
self.tid = Some(tid);
self
}
pub fn issuer(mut self, iss: impl Into<String>) -> Self {
self.iss = Some(iss.into());
self
}
pub fn audience<I, S>(mut self, audiences: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.aud = Some(audiences.into_iter().map(Into::into).collect());
self
}
pub fn subject(mut self, sub: impl Into<String>) -> Self {
self.sub = Some(sub.into());
self
}
pub fn manifest<M: Serialize>(mut self, iss: impl Into<String>, claims: &M) -> Self {
self.manifest_plain = Some(serial::to_manifest_plaintext(&iss.into(), claims));
self
}
pub fn mint(self) -> Result<String, MintError> {
let exp = self.exp.ok_or(MintError::Missing("exp"))?;
if let Some(aud) = &self.aud {
if aud.is_empty() {
return Err(MintError::EmptyAudience);
}
}
let tid = self.tid.unwrap_or_else(Uuid::now_v7);
if tid.get_version_num() != 7 || tid.get_variant() != uuid::Variant::RFC4122 {
return Err(MintError::BadTid);
}
let mandate_plain = zeroize::Zeroizing::new(serial::to_mandate_plaintext(
exp,
tid,
self.iss.as_deref(),
self.aud.as_deref(),
self.sub.as_deref(),
self.app,
)?);
let mandate_sealed = seal(
&mandate_plain,
self.issuer.key.bytes(),
self.issuer.mandate_alg,
)?;
let manifest_sealed = match self.manifest_plain {
Some(result) => Some(seal(&result?, &MANIFEST_KEY, self.issuer.manifest_alg)?),
None => None,
};
let enc = self.issuer.encoding;
let cap = manifest_sealed
.as_ref()
.map_or(0, |s| encoded_len(s.len(), enc) + 1) + 1 + 1 + encoded_len(mandate_sealed.len(), enc);
let mut token = String::with_capacity(cap);
if let Some(sealed) = &manifest_sealed {
encode_into(sealed, enc, &mut token);
token.push(self.issuer.manifest_alg.code());
}
token.push(enc.separator());
token.push(self.issuer.mandate_alg.code());
encode_into(&mandate_sealed, enc, &mut token);
Ok(token)
}
}