use jiff::Timestamp;
use tollgate_core::{KeyId, Principal};
use crate::hmac_registry::{EntropyUnavailable, MintedKey};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Verified {
pub principal: Principal,
pub reusable_until: Option<Timestamp>,
}
impl Verified {
#[must_use]
pub const fn indefinite(principal: Principal) -> Self {
Verified {
principal,
reusable_until: None,
}
}
#[must_use]
pub const fn until(principal: Principal, until: Timestamp) -> Self {
Verified {
principal,
reusable_until: Some(until),
}
}
#[must_use]
pub fn is_reusable_at(&self, now: Timestamp) -> bool {
self.reusable_until.is_none_or(|until| now < until)
}
}
pub trait CredentialVerifier {
fn verify(&self, credential: &[u8]) -> Option<Verified>;
}
pub trait CredentialIssuer {
fn mint(&self, key_id: KeyId) -> Result<MintedKey, EntropyUnavailable>;
}
impl<I: CredentialIssuer + ?Sized> CredentialIssuer for &I {
fn mint(&self, key_id: KeyId) -> Result<MintedKey, EntropyUnavailable> {
(**self).mint(key_id)
}
}
impl<V: CredentialVerifier + ?Sized> CredentialVerifier for &V {
fn verify(&self, credential: &[u8]) -> Option<Verified> {
(**self).verify(credential)
}
}
impl<V: CredentialVerifier + ?Sized> CredentialVerifier for std::sync::Arc<V> {
fn verify(&self, credential: &[u8]) -> Option<Verified> {
(**self).verify(credential)
}
}
impl<V: CredentialVerifier + ?Sized> CredentialVerifier for Box<V> {
fn verify(&self, credential: &[u8]) -> Option<Verified> {
(**self).verify(credential)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
struct Fixed;
impl CredentialVerifier for Fixed {
fn verify(&self, credential: &[u8]) -> Option<Verified> {
(credential == b"good").then(|| Verified::indefinite(Principal(1)))
}
}
#[test]
fn every_forwarding_impl_reaches_the_verifier() {
let expected = Some(Verified::indefinite(Principal(1)));
assert_eq!(CredentialVerifier::verify(&&Fixed, b"good"), expected);
assert_eq!(Arc::new(Fixed).verify(b"good"), expected);
assert_eq!(Box::new(Fixed).verify(b"good"), expected);
let boxed: Box<dyn CredentialVerifier> = Box::new(Fixed);
assert_eq!(boxed.verify(b"good"), expected);
let shared: Arc<dyn CredentialVerifier> = Arc::new(Fixed);
assert_eq!(shared.verify(b"good"), expected);
assert_eq!(boxed.verify(b"bad"), None);
assert_eq!(shared.verify(b"bad"), None);
}
#[test]
fn an_indefinite_answer_is_reusable_at_any_instant() {
let verified = Verified::indefinite(Principal(1));
assert!(verified.is_reusable_at(Timestamp::from_second(0).unwrap()));
assert!(verified.is_reusable_at(Timestamp::MAX));
}
#[test]
fn a_bounded_answer_expires_at_its_instant_not_after_it() {
let until = Timestamp::from_second(60).unwrap();
let verified = Verified::until(Principal(1), until);
assert!(verified.is_reusable_at(Timestamp::from_second(59).unwrap()));
assert!(!verified.is_reusable_at(until));
assert!(!verified.is_reusable_at(Timestamp::from_second(61).unwrap()));
}
}