use std::collections::HashMap;
use polyc_proto::proto::polychrome::persona::v1::{
ExternalIdentity, PersonaCredential, PersonaProfile,
};
use tokio::sync::Mutex;
#[derive(Default)]
pub(crate) struct FakePersonaAuthority {
profiles: Mutex<HashMap<String, PersonaProfile>>,
identities: Mutex<HashMap<String, String>>,
links: Mutex<HashMap<String, String>>,
credentials: Mutex<HashMap<String, PersonaCredential>>,
credential_index: Mutex<HashMap<Vec<u8>, String>>,
wallets: Mutex<HashMap<String, String>>,
}
impl FakePersonaAuthority {
fn identity_key(identity: &ExternalIdentity) -> String {
format!(
"{}:{}:{}",
identity.provider, identity.scope, identity.external_id
)
}
pub(crate) async fn attribute(
&self,
identity: ExternalIdentity,
_conversation_id: String,
_role: String,
_now_ms: u64,
) -> Result<String, String> {
let key = Self::identity_key(&identity);
if let Some(persona_id) = self.identities.lock().await.get(&key) {
return Ok(persona_id.clone());
}
let persona_id = uuid::Uuid::new_v4().to_string();
self.profiles.lock().await.insert(
persona_id.clone(),
PersonaProfile {
persona_id: persona_id.clone(),
status: "provisional".to_owned(),
..Default::default()
},
);
self.identities.lock().await.insert(key, persona_id.clone());
Ok(persona_id)
}
pub(crate) async fn start_link(
&self,
actor: ExternalIdentity,
code: String,
_ttl_ms: u64,
now_ms: u64,
) -> Result<(), String> {
let survivor = self
.attribute(actor, String::new(), String::new(), now_ms)
.await?;
self.links.lock().await.insert(code, survivor);
Ok(())
}
pub(crate) async fn complete_link(
&self,
code: String,
_target: ExternalIdentity,
_now_ms: u64,
) -> Result<polyc_persona::CompleteLinkOutcome, String> {
let persona_id = self
.links
.lock()
.await
.get(&code)
.cloned()
.ok_or_else(|| "unknown link code".to_owned())?;
if let Some(profile) = self.profiles.lock().await.get_mut(&persona_id) {
profile.status = polyc_persona::STATUS_LINKED.to_owned();
}
Ok(polyc_persona::CompleteLinkOutcome::Linked { persona_id })
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn seed_persona_credential_for_tests(
&self,
persona_id: &str,
credential_id: Vec<u8>,
p256_public_key_sec1: Vec<u8>,
signing_public_key: Vec<u8>,
rp_id: String,
origin: String,
now_ms: u64,
) -> Result<PersonaCredential, String> {
let credential = PersonaCredential {
persona_id: persona_id.to_owned(),
credential_id: credential_id.clone(),
p256_public_key_sec1,
signing_public_key,
rp_id,
origin,
created_at_ms: now_ms,
revoked: false,
..Default::default()
};
self.credentials
.lock()
.await
.insert(persona_id.to_owned(), credential.clone());
self.credential_index
.lock()
.await
.insert(credential_id, persona_id.to_owned());
Ok(credential)
}
pub(crate) async fn revoke_persona_credential(
&self,
persona_id: &str,
_now_ms: u64,
) -> Result<Option<PersonaCredential>, String> {
let mut credentials = self.credentials.lock().await;
Ok(credentials.get_mut(persona_id).map(|credential| {
credential.revoked = true;
credential.clone()
}))
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn set_wallet_link(
&self,
persona_id: &str,
wallet_address: String,
_currency: String,
_key_ref: String,
_expiry_unix: u64,
_now_ms: u64,
_webauthn_credential_id: String,
_key_address: String,
_expected_identity: Option<()>,
) -> Result<(), String> {
self.wallets
.lock()
.await
.insert(wallet_address, persona_id.to_owned());
Ok(())
}
}
impl crate::PersonaSessionReader for FakePersonaAuthority {
async fn session_profile(&self, persona_id: String) -> Result<Option<PersonaProfile>, ()> {
Ok(self.profiles.lock().await.get(&persona_id).cloned())
}
async fn session_wallet_persona(&self, wallet_address: &str) -> Result<Option<String>, ()> {
Ok(self.wallets.lock().await.get(wallet_address).cloned())
}
async fn session_credential_persona(&self, credential_id: &[u8]) -> Result<Option<String>, ()> {
Ok(self
.credential_index
.lock()
.await
.get(credential_id)
.cloned())
}
async fn session_credential(&self, persona_id: &str) -> Result<Option<PersonaCredential>, ()> {
Ok(self.credentials.lock().await.get(persona_id).cloned())
}
}