use auths_id::keri::types::Prefix;
use serde::{Deserialize, Serialize};
use crate::context::AuthsContext;
use crate::domains::org::delegation::{OrgKelSnapshot, list_members};
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)
}
}
pub fn classify_authority_at_signing(
ctx: &AuthsContext,
org_prefix: &Prefix,
member_prefix: &Prefix,
signed_at: Option<u128>,
) -> Result<AuthorityAtSigning, OrgError> {
let snapshot = OrgKelSnapshot::load(ctx, org_prefix)?;
classify_authority_at_signing_with(&snapshot, member_prefix, signed_at)
}
pub fn classify_authority_at_signing_with(
snapshot: &OrgKelSnapshot,
member_prefix: &Prefix,
signed_at: Option<u128>,
) -> Result<AuthorityAtSigning, OrgError> {
let Some(authority) = snapshot.member_authority(member_prefix) else {
return Ok(AuthorityAtSigning::NeverDelegated);
};
if !authority.revoked {
return Ok(AuthorityAtSigning::AuthorizedBeforeRevocation);
}
let revoked_at = find_revocation_event(snapshot.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)
}