use anyhow::{Context, Result};
use aries_askar::entry::TagFilter;
use aries_askar::storage::backend::OrderBy;
use aries_askar::{ErrorKind, PassKey, Session, Store, StoreKeyMethod};
use crate::config::Config;
use crate::credo::category;
use crate::progress::db_wait;
pub async fn open(cfg: &Config) -> Result<Store> {
let key_method = StoreKeyMethod::parse_uri(&cfg.key_derivation_method).with_context(|| {
format!(
"KEY_DERIVATION_METHOD={:?} is not a key method Askar recognises \
(expected 'raw', 'kdf:argon2i:mod', 'kdf:argon2i:int' or 'none')",
cfg.key_derivation_method
)
})?;
let uri = cfg.store_uri();
let pass_key = PassKey::from(cfg.wallet_key.as_str());
let store = db_wait("store", Store::open(&uri, Some(key_method), pass_key, None))
.await
.map_err(|e| {
let msg = e.to_string();
if msg.contains("key method mismatch") {
anyhow::anyhow!(
"KEY_DERIVATION_METHOD={:?} is not how this store was provisioned.\n\
Read the real method straight out of the store (it is not secret):\n\
\n SELECT split_part(value, '?', 1) FROM config WHERE name='key';\n\
\n\
It returns e.g. 'kdf:argon2i:13:mod' -> use KEY_DERIVATION_METHOD=kdf:argon2i:mod,\n\
or 'raw' -> use KEY_DERIVATION_METHOD=raw.\n\
Note Credo defaults to kdf:argon2i:mod when keyDerivationMethod is unset.\n\
Underlying: {e}",
cfg.key_derivation_method
)
} else if e.kind() == ErrorKind::Encryption {
anyhow::anyhow!(
"Askar rejected the store key. WALLET_KEY must match the agent's \
exactly (KEY_DERIVATION_METHOD={:?} was accepted, so it is the key \
itself that is wrong). Underlying: {e}",
cfg.key_derivation_method
)
} else {
anyhow::anyhow!("Could not open the Askar store: {e}")
}
})?;
Ok(store)
}
pub async fn list_tenants(store: &Store) -> Result<Vec<String>> {
let mut scan = db_wait(
"root",
store.scan(
None, Some(category::TENANT.to_string()),
None,
None,
None,
Some(OrderBy::Id),
false,
),
)
.await
.context(
"Cannot enumerate tenants: scanning the TenantRecord category failed. \
Set TENANT_ALLOWLIST=id1,id2,... or TENANT_ID=<id> to target tenants explicitly.",
)?;
let mut ids = Vec::new();
while let Some(batch) = scan.fetch_next().await? {
for entry in batch {
ids.push(entry.name);
}
}
Ok(ids)
}
pub async fn profile_exists(store: &Store, profile: &str) -> Result<bool> {
let profiles = store
.list_profiles()
.await
.context("could not list store profiles")?;
Ok(profiles.iter().any(|p| p == profile))
}
pub async fn count_category(
session: &mut Session,
category: &str,
tag_filter: Option<TagFilter>,
) -> Option<i64> {
session.count(Some(category), tag_filter).await.ok()
}
pub async fn preflight(session: &mut Session, label: &str) -> Result<bool> {
let mut purgeable_total = 0i64;
let mut protected_total = 0i64;
for cat in category::PURGEABLE {
purgeable_total += count_category(session, cat, None).await.unwrap_or(0);
}
for cat in category::PROTECTED {
protected_total += count_category(session, cat, None).await.unwrap_or(0);
}
let suspicious = purgeable_total == 0 && protected_total > 0;
if suspicious {
eprintln!(
"\u{26a0}\u{fe0f} [{label}] PREFLIGHT: every purgeable category is empty, but the \
protected categories hold {protected_total} records."
);
eprintln!(
" That usually means the Askar category names in credo.rs have drifted from the \
Credo version the agent runs. Re-check the `.type` statics before trusting a \
zero-result run. Expected categories: {:?}",
category::PURGEABLE
);
}
Ok(suspicious)
}