use std::sync::Arc;
use async_trait::async_trait;
use crate::core::{RunId, Secret, Seq, StoreError, TenantId};
use crate::journal::payload;
use crate::push::{DueBatch, PushConfig, PushNamespace, PushRegistration, PushStore};
use super::KeyRing;
#[derive(Debug)]
pub struct SealedPush {
inner: Arc<dyn PushStore>,
keys: Arc<dyn KeyRing>,
tenant: TenantId,
}
impl SealedPush {
#[must_use]
pub fn wrap(inner: Arc<dyn PushStore>, keys: Arc<dyn KeyRing>, tenant: TenantId) -> Arc<Self> {
super::assert_serves(inner.tenant(), &tenant, "push");
Arc::new(Self {
inner,
keys,
tenant,
})
}
fn scope(&self) -> String {
super::scope(&self.tenant, "push")
}
pub(super) fn aad(tenant: &TenantId, task: RunId, id: &str) -> String {
format!("push:{tenant}:{task}/{id}")
}
async fn sealed_secret(&self, aad: &str, secret: &Secret) -> Result<Secret, StoreError> {
let envelope = super::envelope::seal(
self.keys.as_ref(),
&self.scope(),
aad.as_bytes(),
secret.expose().as_bytes(),
)
.await
.map_err(|e| StoreError::Backend(format!("sealing a webhook credential failed: {e}")))?;
Ok(Secret::new(payload::wrap_text(&envelope)))
}
async fn sealed(&self, config: &PushConfig) -> Result<PushConfig, StoreError> {
let aad = Self::aad(&self.tenant, config.task, &config.id);
let mut sealed = config.clone();
if let Some(token) = &config.token {
sealed.token = Some(self.sealed_secret(&aad, token).await?);
}
if let Some(authentication) = &mut sealed.authentication {
authentication.credentials = self
.sealed_secret(&aad, &authentication.credentials)
.await?;
}
Ok(sealed)
}
async fn opened_secret(&self, aad: &str, secret: Secret) -> Option<Secret> {
let Some(envelope) = payload::unwrap_text(secret.expose()) else {
return Some(secret);
};
let plain = super::envelope::open(self.keys.as_ref(), aad.as_bytes(), &envelope)
.await
.ok()?;
String::from_utf8(plain).ok().map(Secret::new)
}
async fn opened(&self, mut config: PushConfig) -> PushConfig {
let aad = Self::aad(&self.tenant, config.task, &config.id);
if let Some(token) = config.token.take() {
config.token = self.opened_secret(&aad, token).await;
}
if let Some(authentication) = config.authentication.take() {
config.authentication = self
.opened_secret(&aad, authentication.credentials)
.await
.map(|credentials| crate::push::PushAuthentication {
scheme: authentication.scheme,
credentials,
});
}
config
}
async fn opened_all(&self, rows: Vec<PushRegistration>) -> Vec<PushRegistration> {
let mut out = Vec::with_capacity(rows.len());
for mut registration in rows {
registration.config = self.opened(registration.config).await;
out.push(registration);
}
out
}
}
#[async_trait]
impl PushStore for SealedPush {
fn tenant(&self) -> &str {
self.tenant.as_str()
}
async fn put(&self, config: &PushConfig, next_seq: Seq) -> Result<(), StoreError> {
self.inner.put(&self.sealed(config).await?, next_seq).await
}
async fn get(&self, task: RunId, id: &str) -> Result<Option<PushConfig>, StoreError> {
let found = self.inner.get(task, id).await?;
Ok(match found {
Some(config) => Some(self.opened(config).await),
None => None,
})
}
async fn list(&self, task: RunId) -> Result<Vec<PushConfig>, StoreError> {
let configs = self.inner.list(task).await?;
let mut out = Vec::with_capacity(configs.len());
for config in configs {
out.push(self.opened(config).await);
}
Ok(out)
}
async fn due(&self, at: u64, limit: usize) -> Result<Vec<PushRegistration>, StoreError> {
let rows = self.inner.due(at, limit).await?;
Ok(self.opened_all(rows).await)
}
async fn due_in(
&self,
at: u64,
limit: usize,
namespace: PushNamespace,
) -> Result<DueBatch, StoreError> {
let mut batch = self.inner.due_in(at, limit, namespace).await?;
batch.rows = self.opened_all(batch.rows).await;
Ok(batch)
}
async fn advance(&self, task: RunId, id: &str, next_seq: Seq) -> Result<(), StoreError> {
self.inner.advance(task, id, next_seq).await
}
async fn retry(
&self,
task: RunId,
id: &str,
next_attempt_at: u64,
error: &str,
) -> Result<(), StoreError> {
self.inner.retry(task, id, next_attempt_at, error).await
}
async fn park(&self, task: RunId, id: &str, error: &str) -> Result<(), StoreError> {
self.inner.park(task, id, error).await
}
async fn parked(&self, limit: usize) -> Result<Vec<PushRegistration>, StoreError> {
let rows = self.inner.parked(limit).await?;
Ok(self.opened_all(rows).await)
}
async fn unpark(&self, task: RunId, id: &str, at: u64) -> Result<bool, StoreError> {
self.inner.unpark(task, id, at).await
}
async fn delete(&self, task: RunId, id: &str) -> Result<(), StoreError> {
self.inner.delete(task, id).await
}
}