use super::identity::{AgentIdentity, PublicIdentity};
use crate::CellError;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthToken {
pub token_id: [u8; 16],
pub agent_id: [u8; 16],
pub token_value: Vec<u8>,
pub expires_at: u64,
pub permissions: Vec<String>,
pub metadata: TokenMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenMetadata {
pub created_at: u64,
pub issuer: Option<[u8; 16]>,
pub purpose: String,
}
impl AuthToken {
pub fn create(
identity: &AgentIdentity,
duration_secs: u64,
permissions: Vec<String>,
purpose: String,
) -> Result<Self, CellError> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|_| CellError::InvalidState("System time error".to_string()))?
.as_secs();
let token_id = Self::generate_token_id();
let token_value = identity.sign(&token_id)?;
Ok(Self {
token_id,
agent_id: identity.agent_id(),
token_value,
expires_at: now + duration_secs,
permissions,
metadata: TokenMetadata {
created_at: now,
issuer: None,
purpose,
},
})
}
fn generate_token_id() -> [u8; 16] {
use rand::Rng;
let mut rng = rand::rng();
let mut token_id = [0u8; 16];
rng.fill(&mut token_id);
token_id
}
pub fn is_expired(&self) -> bool {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
now >= self.expires_at
}
pub fn verify(&self) -> Result<bool, CellError> {
if self.is_expired() {
return Ok(false);
}
Ok(!self.token_value.is_empty())
}
pub fn verify_with_identity(&self, identity: &PublicIdentity) -> Result<bool, CellError> {
if self.is_expired() {
return Ok(false);
}
if self.agent_id != identity.agent_id {
return Ok(false);
}
identity.verify_signature(&self.token_id, &self.token_value)
}
pub fn has_permission(&self, permission: &str) -> bool {
self.permissions.iter().any(|p| p == permission)
}
pub fn remaining_secs(&self) -> u64 {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
self.expires_at.saturating_sub(now)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthChallenge {
pub challenge_id: [u8; 16],
pub nonce: Vec<u8>,
pub timestamp: u64,
pub validity_secs: u64,
}
impl AuthChallenge {
pub fn create(validity_secs: u64) -> Result<Self, CellError> {
use rand::Rng;
let mut rng = rand::rng();
let mut challenge_id = [0u8; 16];
rng.fill(&mut challenge_id);
let mut nonce = vec![0u8; 32];
rng.fill(&mut nonce[..]);
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|_| CellError::InvalidState("System time error".to_string()))?
.as_secs();
Ok(Self {
challenge_id,
nonce,
timestamp,
validity_secs,
})
}
pub fn is_valid(&self) -> bool {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
now < self.timestamp + self.validity_secs
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthResponse {
pub challenge_id: [u8; 16],
pub agent_id: [u8; 16],
pub signature: Vec<u8>,
pub timestamp: u64,
}
impl AuthResponse {
pub fn create(challenge: &AuthChallenge, identity: &AgentIdentity) -> Result<Self, CellError> {
let mut message = challenge.nonce.clone();
message.extend_from_slice(&identity.agent_id());
let signature = identity.sign(&message)?;
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|_| CellError::InvalidState("System time error".to_string()))?
.as_secs();
Ok(Self {
challenge_id: challenge.challenge_id,
agent_id: identity.agent_id(),
signature,
timestamp,
})
}
pub fn verify(
&self,
challenge: &AuthChallenge,
identity: &PublicIdentity,
) -> Result<bool, CellError> {
if !challenge.is_valid() {
return Ok(false);
}
if self.challenge_id != challenge.challenge_id {
return Ok(false);
}
if self.agent_id != identity.agent_id {
return Ok(false);
}
let mut message = challenge.nonce.clone();
message.extend_from_slice(&self.agent_id);
identity.verify_signature(&message, &self.signature)
}
}
#[derive(Debug)]
pub struct Authenticator {
challenges: HashMap<[u8; 16], AuthChallenge>,
sessions: HashMap<[u8; 16], AuthToken>,
identities: HashMap<[u8; 16], PublicIdentity>,
}
impl Authenticator {
pub fn new() -> Self {
Self {
challenges: HashMap::new(),
sessions: HashMap::new(),
identities: HashMap::new(),
}
}
pub fn register_identity(&mut self, identity: PublicIdentity) {
self.identities.insert(identity.agent_id, identity);
}
pub fn create_challenge(&mut self, validity_secs: u64) -> Result<AuthChallenge, CellError> {
let challenge = AuthChallenge::create(validity_secs)?;
self.challenges
.insert(challenge.challenge_id, challenge.clone());
Ok(challenge)
}
pub fn verify_response(
&mut self,
response: &AuthResponse,
token_duration_secs: u64,
) -> Result<AuthToken, CellError> {
let challenge = self
.challenges
.get(&response.challenge_id)
.ok_or_else(|| CellError::InvalidState("Challenge not found".to_string()))?;
let identity = self
.identities
.get(&response.agent_id)
.ok_or_else(|| CellError::InvalidState("Identity not registered".to_string()))?;
if !response.verify(challenge, identity)? {
return Err(CellError::InvalidState("Invalid response".to_string()));
}
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|_| CellError::InvalidState("System time error".to_string()))?
.as_secs();
let token = AuthToken {
token_id: response.challenge_id, agent_id: response.agent_id,
token_value: response.signature.clone(),
expires_at: now + token_duration_secs,
permissions: vec!["default".to_string()],
metadata: TokenMetadata {
created_at: now,
issuer: None,
purpose: "authenticated_session".to_string(),
},
};
self.sessions.insert(response.agent_id, token.clone());
self.challenges.remove(&response.challenge_id);
Ok(token)
}
pub fn verify_token(&self, token: &AuthToken) -> Result<bool, CellError> {
if token.is_expired() {
return Ok(false);
}
if let Some(session) = self.sessions.get(&token.agent_id) {
Ok(session.token_id == token.token_id)
} else {
Ok(false)
}
}
pub fn get_session(&self, agent_id: &[u8; 16]) -> Option<&AuthToken> {
self.sessions.get(agent_id)
}
pub fn revoke_session(&mut self, agent_id: &[u8; 16]) -> bool {
self.sessions.remove(agent_id).is_some()
}
pub fn cleanup_expired(&mut self) {
self.challenges.retain(|_, c| c.is_valid());
self.sessions.retain(|_, t| !t.is_expired());
}
pub fn active_sessions(&self) -> usize {
self.sessions.len()
}
pub fn pending_challenges(&self) -> usize {
self.challenges.len()
}
}
impl Default for Authenticator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::security::identity::AgentIdentity;
#[test]
fn test_auth_token_creation() {
let identity = AgentIdentity::generate();
let token = AuthToken::create(
&identity,
3600,
vec!["read".to_string()],
"test".to_string(),
)
.expect("Failed to create token");
assert_eq!(token.agent_id, identity.agent_id());
assert!(token.has_permission("read"));
assert!(!token.is_expired());
}
#[test]
fn test_auth_token_expiration() {
let identity = AgentIdentity::generate();
let token = AuthToken::create(&identity, 0, vec![], "test".to_string())
.expect("Failed to create token");
std::thread::sleep(std::time::Duration::from_millis(10));
assert!(token.is_expired());
}
#[test]
fn test_auth_token_verify_with_identity() {
let identity = AgentIdentity::generate();
let token = AuthToken::create(&identity, 3600, vec![], "test".to_string())
.expect("Failed to create token");
let public = identity.public_identity();
let valid = token
.verify_with_identity(&public)
.expect("Failed to verify");
assert!(valid);
}
#[test]
fn test_auth_challenge_creation() {
let challenge = AuthChallenge::create(60).expect("Failed to create challenge");
assert_eq!(challenge.nonce.len(), 32);
assert!(challenge.is_valid());
}
#[test]
fn test_auth_response_creation() {
let challenge = AuthChallenge::create(60).expect("Failed to create challenge");
let identity = AgentIdentity::generate();
let response =
AuthResponse::create(&challenge, &identity).expect("Failed to create response");
assert_eq!(response.challenge_id, challenge.challenge_id);
assert_eq!(response.agent_id, identity.agent_id());
}
#[test]
fn test_auth_response_verify() {
let challenge = AuthChallenge::create(60).expect("Failed to create challenge");
let identity = AgentIdentity::generate();
let public = identity.public_identity();
let response =
AuthResponse::create(&challenge, &identity).expect("Failed to create response");
let valid = response
.verify(&challenge, &public)
.expect("Failed to verify");
assert!(valid);
}
#[test]
fn test_authenticator_challenge_response_flow() {
let mut authenticator = Authenticator::new();
let identity = AgentIdentity::generate();
let public = identity.public_identity();
authenticator.register_identity(public);
let challenge = authenticator
.create_challenge(60)
.expect("Failed to create challenge");
let response =
AuthResponse::create(&challenge, &identity).expect("Failed to create response");
let token = authenticator
.verify_response(&response, 3600)
.expect("Failed to verify response");
assert_eq!(token.agent_id, identity.agent_id());
assert_eq!(authenticator.active_sessions(), 1);
}
#[test]
fn test_authenticator_verify_token() {
let mut authenticator = Authenticator::new();
let identity = AgentIdentity::generate();
let public = identity.public_identity();
authenticator.register_identity(public);
let challenge = authenticator.create_challenge(60).expect("Failed");
let response = AuthResponse::create(&challenge, &identity).expect("Failed");
let token = authenticator
.verify_response(&response, 3600)
.expect("Failed");
let valid = authenticator
.verify_token(&token)
.expect("Failed to verify");
assert!(valid);
}
#[test]
fn test_authenticator_revoke_session() {
let mut authenticator = Authenticator::new();
let identity = AgentIdentity::generate();
let public = identity.public_identity();
authenticator.register_identity(public);
let challenge = authenticator.create_challenge(60).expect("Failed");
let response = AuthResponse::create(&challenge, &identity).expect("Failed");
authenticator
.verify_response(&response, 3600)
.expect("Failed");
assert_eq!(authenticator.active_sessions(), 1);
let revoked = authenticator.revoke_session(&identity.agent_id());
assert!(revoked);
assert_eq!(authenticator.active_sessions(), 0);
}
#[test]
fn test_authenticator_cleanup_expired() {
let mut authenticator = Authenticator::new();
let mut challenge = AuthChallenge::create(1).expect("Failed");
challenge.validity_secs = 0;
authenticator
.challenges
.insert(challenge.challenge_id, challenge);
assert_eq!(authenticator.pending_challenges(), 1);
authenticator.cleanup_expired();
assert_eq!(authenticator.pending_challenges(), 0);
}
}