use std::ops::ControlFlow;
use auths_id::keri::Event;
use auths_id::keri::types::Prefix;
use serde::{Deserialize, Serialize};
use crate::context::AuthsContext;
use crate::domains::org::delegation::{list_members, resolve_member_authority};
use crate::domains::org::error::OrgError;
use crate::domains::org::offboarding::{
SignedOffboardingRecord, find_revocation_event, load_offboarding_record,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "authority_at_signing", rename_all = "snake_case")]
pub enum AuthorityAtSigning {
AuthorizedBeforeRevocation,
RejectedAfterRevocation {
#[serde(with = "u128_str")]
revoked_at: u128,
},
RejectedRevokedPositionUnknown {
#[serde(with = "u128_str")]
revoked_at: u128,
},
NeverDelegated,
}
mod u128_str {
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(value: &u128, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&value.to_string())
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u128, D::Error> {
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
fn collect_org_kel(ctx: &AuthsContext, org_prefix: &Prefix) -> Vec<Event> {
let mut events = Vec::new();
let _ = ctx.registry.visit_events(org_prefix, 0, &mut |e| {
events.push(e.clone());
ControlFlow::Continue(())
});
events
}
pub fn classify_authority_at_signing(
ctx: &AuthsContext,
org_prefix: &Prefix,
member_prefix: &Prefix,
signed_at: Option<u128>,
) -> Result<AuthorityAtSigning, OrgError> {
let Some(authority) = resolve_member_authority(ctx, org_prefix, member_prefix)? else {
return Ok(AuthorityAtSigning::NeverDelegated);
};
if !authority.revoked {
return Ok(AuthorityAtSigning::AuthorizedBeforeRevocation);
}
let org_kel = collect_org_kel(ctx, org_prefix);
let revoked_at = find_revocation_event(&org_kel, member_prefix)
.map(|(_, seq)| seq)
.ok_or_else(|| {
OrgError::Signing("revoked member has no revocation event on the org KEL".to_string())
})?;
Ok(match signed_at {
None => AuthorityAtSigning::RejectedRevokedPositionUnknown { revoked_at },
Some(seq) if seq < revoked_at => AuthorityAtSigning::AuthorizedBeforeRevocation,
Some(_) => AuthorityAtSigning::RejectedAfterRevocation { revoked_at },
})
}
pub fn list_offboarding_records(
ctx: &AuthsContext,
org_prefix: &Prefix,
) -> Result<Vec<SignedOffboardingRecord>, OrgError> {
let mut records = Vec::new();
for member in list_members(ctx, org_prefix)?
.into_iter()
.filter(|m| m.revoked)
{
let member_prefix = Prefix::new_unchecked(member.member_prefix.clone());
if let Some(record) = load_offboarding_record(ctx, org_prefix, &member_prefix)? {
records.push(record);
}
}
Ok(records)
}