use crate::passkey::PasskeyError;
use crate::storage::{CacheData, CacheKey, CachePrefix, get_data};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct AuthenticatorInfo {
pub name: String,
pub icon_dark: Option<String>,
pub icon_light: Option<String>,
}
impl Default for AuthenticatorInfo {
fn default() -> Self {
Self {
name: "Unknown Authenticator".to_string(),
icon_dark: None,
icon_light: None,
}
}
}
impl From<AuthenticatorInfo> for CacheData {
fn from(info: AuthenticatorInfo) -> Self {
let value = serde_json::to_string(&info).unwrap_or_else(|_| "{}".to_string()); CacheData { value }
}
}
impl TryFrom<CacheData> for AuthenticatorInfo {
type Error = PasskeyError;
fn try_from(cache_data: CacheData) -> Result<Self, Self::Error> {
serde_json::from_str(&cache_data.value).map_err(|e| PasskeyError::Storage(e.to_string()))
}
}
#[derive(Debug, Deserialize, Clone)]
pub struct AaguidMap(pub HashMap<String, AuthenticatorInfo>);
const AAGUID_JSON: &str = include_str!("../../../assets/aaguid.json");
const AAGUID_URL: &str = "https://raw.githubusercontent.com/passkeydeveloper/passkey-authenticator-aaguids/refs/heads/main/combined_aaguid.json";
pub(crate) async fn store_aaguids() -> Result<(), PasskeyError> {
tracing::info!("Loading AAGUID mappings from JSON");
let json = AAGUID_JSON.to_string();
store_aaguid_in_cache(json).await?;
let client = crate::utils::get_client();
let response = client
.get(AAGUID_URL)
.send()
.await
.map_err(|e| PasskeyError::Storage(e.to_string()))?;
let json = response
.text()
.await
.map_err(|e| PasskeyError::Storage(e.to_string()))?;
store_aaguid_in_cache(json).await?;
Ok(())
}
async fn store_aaguid_in_cache(json: String) -> Result<(), PasskeyError> {
let aaguid_map: AaguidMap = serde_json::from_str(&json).map_err(|e| {
tracing::error!("Failed to parse AAGUID JSON: {}", e);
PasskeyError::Storage(e.to_string())
})?;
for (aaguid, info) in &aaguid_map.0 {
let cache_prefix = CachePrefix::aaguid();
let cache_key =
CacheKey::new(aaguid.to_string()).map_err(|e| PasskeyError::Storage(e.to_string()))?;
crate::storage::store_cache_keyed::<_, PasskeyError>(
cache_prefix,
cache_key,
info.clone(),
31536000,
)
.await
.map_err(|e| {
tracing::error!("Failed to store AAGUID {} in cache: {}", aaguid, e);
e
})?;
}
tracing::info!(
"Successfully loaded {} AAGUID mappings into cache",
aaguid_map.0.len()
);
Ok(())
}
pub async fn get_authenticator_info(
aaguid: &str,
) -> Result<Option<AuthenticatorInfo>, PasskeyError> {
let cache_prefix = CachePrefix::aaguid();
let cache_key =
CacheKey::new(aaguid.to_string()).map_err(|e| PasskeyError::Storage(e.to_string()))?;
get_data::<AuthenticatorInfo, PasskeyError>(cache_prefix, cache_key).await
}
pub async fn get_authenticator_info_batch(
aaguids: &[String],
) -> Result<HashMap<String, AuthenticatorInfo>, PasskeyError> {
let mut result = HashMap::new();
for aaguid in aaguids {
let cache_prefix = CachePrefix::aaguid();
if let Ok(cache_key) = CacheKey::new(aaguid.clone())
&& let Ok(Some(info)) =
get_data::<AuthenticatorInfo, PasskeyError>(cache_prefix, cache_key).await
{
result.insert(aaguid.clone(), info);
}
}
Ok(result)
}
#[cfg(test)]
mod tests;