Files
buzz/crates/buzz-audit/src/entry.rs
Tyler 14fba21e57 Multi-tenant Buzz relay: community_id as a server-resolved key (comprehensive rewrite) (#1321)
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Mari <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Quinn <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: Sami <sami@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co>
2026-06-29 12:39:02 -04:00

73 lines
3.4 KiB
Rust

use buzz_core::CommunityId;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::action::AuditAction;
/// A materialised audit log entry as stored in `audit_log`.
///
/// Rows are keyed `(community_id, seq)`: `seq` is monotonic *within one
/// community*, and `prev_hash` chains to the previous entry *of the same
/// community*. The chain is independent per tenant.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuditEntry {
/// Server-resolved community this entry belongs to. Leads the primary key.
pub community_id: Uuid,
/// Sequence number, monotonic within `community_id` (starts at 1).
pub seq: i64,
/// SHA-256 of this entry's fields including `community_id` and `prev_hash`.
pub hash: Vec<u8>,
/// SHA-256 of the previous entry in *this community's* chain, or `None` for
/// the community's first entry (hashed as [`crate::hash::GENESIS_HASH`]).
pub prev_hash: Option<Vec<u8>>,
/// Action that was performed.
pub action: AuditAction,
/// Raw bytes of the actor's Nostr pubkey, if the action has one.
pub actor_pubkey: Option<Vec<u8>>,
/// Generic identifier of the object acted upon (event id hex, channel UUID,
/// media sha256, …), if any. The relay resolves it under `community_id`;
/// it never names an object in another community.
pub object_id: Option<String>,
/// Arbitrary JSON context. **Included in the hash** (serialized with sorted
/// keys for determinism) so tampering with it is detectable.
pub detail: serde_json::Value,
/// When the entry was recorded.
pub created_at: DateTime<Utc>,
}
/// Input for appending a new audit entry. `seq`, `prev_hash`, `hash`, and
/// `created_at` are assigned by [`crate::service::AuditService::log`].
///
/// `community_id` is the **server-resolved** tenant (from the request's
/// `TenantContext`), never a client-supplied value — the same provenance rule
/// the whole multi-tenant model rests on.
///
/// Not `Serialize`/`Deserialize`: this is an in-process input struct (consumed
/// by `AuditService::log`, threaded through the in-memory audit sink), never
/// crossing a wire or DB boundary as a whole. Keeping it non-deserializable
/// reinforces the fence — there is no path by which a client-supplied blob
/// becomes a `NewAuditEntry` (and thus a `CommunityId`).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewAuditEntry {
/// Server-resolved community this entry belongs to. Typed as [`CommunityId`]
/// (not a raw `Uuid`) so the provenance rule is visible in the signature:
/// the only ways to obtain one are host resolution or a server-scoped DB
/// row — never a value parsed from client input.
pub community_id: CommunityId,
/// Action that was performed.
pub action: AuditAction,
/// Raw bytes of the actor's Nostr pubkey, if the action has one.
pub actor_pubkey: Option<Vec<u8>>,
/// Generic identifier of the object acted upon, if any.
pub object_id: Option<String>,
/// Arbitrary JSON context included in the hash.
///
/// **Never bearer-token material.** This field is opaque to the audit
/// crate and persisted verbatim; callers must not write tokens, passwords,
/// or other secrets here. `AuthSuccess`/`AuthFailure` entries carry only
/// outcome metadata — the token has no slot in this type, and `detail` must
/// not become one.
pub detail: serde_json::Value,
}