use crate::key_registry::{load_ed25519_yaml, KeyRegistryError, StaticEd25519KeyRegistry};
use ed25519_dalek::VerifyingKey;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
use thiserror::Error;
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct ApproverId(pub String);
impl std::fmt::Display for ApproverId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ApproverRegistryError {
#[error(transparent)]
Load(#[from] KeyRegistryError),
}
pub trait ApproverRegistry: Send + Sync {
fn lookup(&self, approver: &ApproverId) -> Option<VerifyingKey>;
}
pub struct StaticApproverRegistry {
inner: StaticEd25519KeyRegistry<ApproverId>,
}
impl StaticApproverRegistry {
#[must_use]
pub fn from_map(keys: HashMap<ApproverId, VerifyingKey>) -> Self {
Self {
inner: StaticEd25519KeyRegistry::from_map(keys),
}
}
pub fn from_yaml(path: impl AsRef<Path>) -> Result<Self, ApproverRegistryError> {
let keys = load_ed25519_yaml(path, ApproverId)?;
Ok(Self {
inner: StaticEd25519KeyRegistry::from_map(keys),
})
}
}
impl ApproverRegistry for StaticApproverRegistry {
fn lookup(&self, approver: &ApproverId) -> Option<VerifyingKey> {
use crate::key_registry::Ed25519KeyRegistry as _;
self.inner.lookup(approver)
}
}
#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::SigningKey;
fn deterministic_key(seed: u8) -> SigningKey {
SigningKey::from_bytes(&[seed; 32])
}
#[test]
fn round_trip_via_yaml() {
let sk = deterministic_key(1);
let pk = sk.verifying_key();
use base64::Engine as _;
let pk_b64 = base64::engine::general_purpose::STANDARD.encode(pk.to_bytes());
let yaml = format!("alice@insurer.de: \"{pk_b64}\"\n");
let path = std::env::temp_dir().join("klieo-test-approvers.yaml");
std::fs::write(&path, yaml).unwrap();
let reg = StaticApproverRegistry::from_yaml(&path).expect("load");
let resolved = reg
.lookup(&ApproverId("alice@insurer.de".into()))
.expect("found");
assert_eq!(resolved.to_bytes(), pk.to_bytes());
}
#[test]
fn missing_approver_returns_none() {
let reg = StaticApproverRegistry::from_map(HashMap::new());
assert!(reg.lookup(&ApproverId("ghost".into())).is_none());
}
}