//! User resolution domain logic — username <-> identity key lookups. use std::sync::{Arc, Mutex}; use quicprochat_kt::{MerkleLog, RevocationLog, RevocationReason}; use crate::storage::Store; use super::types::*; /// Domain service for user/identity resolution. pub struct UserService { pub store: Arc, pub kt_log: Arc>, pub revocation_log: Arc>, } impl UserService { pub fn resolve_user(&self, req: ResolveUserReq) -> Result { if req.username.is_empty() { return Err(DomainError::EmptyUsername); } let identity_key = self .store .get_user_identity_key(&req.username)? .unwrap_or_default(); let mut inclusion_proof = Vec::new(); if !identity_key.is_empty() { if let Ok(log) = self.kt_log.lock() { if let Some(leaf_idx) = log.find(&req.username, &identity_key) { if let Ok(proof) = log.inclusion_proof(leaf_idx) { if let Ok(bytes) = proof.to_bytes() { inclusion_proof = bytes; } } } } } Ok(ResolveUserResp { identity_key, inclusion_proof, }) } pub fn resolve_identity( &self, req: ResolveIdentityReq, ) -> Result { if req.identity_key.len() != 32 { return Err(DomainError::InvalidIdentityKey(req.identity_key.len())); } let username = self .store .resolve_identity_key(&req.identity_key)? .unwrap_or_default(); Ok(ResolveIdentityResp { username }) } /// Revoke an identity key in the Key Transparency log. pub fn revoke_key(&self, req: RevokeKeyReq) -> Result { if req.identity_key.len() != 32 { return Err(DomainError::InvalidIdentityKey(req.identity_key.len())); } let reason = RevocationReason::from_tag(&req.reason) .ok_or_else(|| DomainError::BadParams(format!("invalid revocation reason: {}", req.reason)))?; let timestamp_ms = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .map(|d| d.as_millis() as u64) .unwrap_or(0); let mut kt = self.kt_log.lock().map_err(|e| DomainError::Io(e.to_string()))?; let mut revlog = self.revocation_log.lock().map_err(|e| DomainError::Io(e.to_string()))?; let leaf_index = revlog .revoke(&mut kt, &req.identity_key, reason, timestamp_ms) .map_err(|e| DomainError::BadParams(e.to_string()))?; // Persist updated logs. if let Ok(bytes) = kt.to_bytes() { let _ = self.store.save_kt_log(bytes); } if let Ok(bytes) = revlog.to_bytes() { let _ = self.store.save_revocation_log(bytes); } Ok(RevokeKeyResp { success: true, leaf_index, }) } /// Check if an identity key has been revoked. pub fn check_revocation(&self, req: CheckRevocationReq) -> Result { let revlog = self.revocation_log.lock().map_err(|e| DomainError::Io(e.to_string()))?; if let Some(entry) = revlog.get(&req.identity_key) { Ok(CheckRevocationResp { revoked: true, reason: entry.reason.as_tag().to_string(), timestamp_ms: entry.timestamp_ms, }) } else { Ok(CheckRevocationResp { revoked: false, reason: String::new(), timestamp_ms: 0, }) } } /// Return a range of KT log entries for client-side audit. pub fn audit_key_transparency( &self, req: AuditKeyTransparencyReq, ) -> Result { let kt = self.kt_log.lock().map_err(|e| DomainError::Io(e.to_string()))?; let end = if req.end == 0 { kt.len() } else { req.end }; let log_entries = kt.audit_log(req.start, end); let entries: Vec = log_entries .into_iter() .map(|(index, hash)| AuditLogEntry { index, leaf_hash: hash.to_vec(), }) .collect(); let tree_size = kt.len(); let root = kt.root().map(|r| r.to_vec()).unwrap_or_default(); Ok(AuditKeyTransparencyResp { entries, tree_size, root, }) } }